Modify structure of deeply nested object into an array of objects - javascript

I have a deeply nested javascript object, which can contain a properties field- which is an object where the key represent the property name and the values represent the rest of the property data. I want to transform the property object into an array of objects where the key is combined with the values.
For example, this is what i have:
const test = {
properties: {
meta: {
type: 'object',
properties: {
agencyId: {
type: 'string',
example: 'e767c439-08bf-48fa-a03c-ac4a09eeee8f',
description: 'agencyId',
},
},
},
data: {
type: 'object',
properties: {
intervalStartTime: {
type: 'string',
description: 'Time in GMT',
example: '1591702200000',
},
group: {
type: 'object',
properties: {
groupType: {
type: 'string',
description: 'Group Type',
example: 'vip',
},
numberofRequests: {
type: 'number',
example: 10198,
description: 'Amount of requests coming from group',
},
},
},
},
},
},
}
And this is what i want:
const test = {
properties: [
{
name: 'meta',
type: 'object',
properties: [
[
{
type: 'string',
example: 'e767c439-08bf-48fa-a03c-ac4a09eeee8f',
description: 'agencyId',
name: 'agencyId',
},
],
],
},
{
name: 'data',
type: 'object',
properties: [
[
{
type: 'string',
description: 'Time in GMT',
example: '1591702200000',
name: 'intervalStartTime',
},
{
name: 'group',
type: 'object',
properties: [
{
name: 'groupType',
type: 'string',
description: 'Group Type',
example: 'vip',
},
{
name: 'numberOfRequests',
type: 'number',
example: 10198,
description: 'Amount of requests coming from group',
},
],
},
],
],
},
],
}
I have a helper function that converts the objects into the form that I want, but I am struggling with recursively modifying the nested properties. This is what I have. Any suggestions on how I can modify the entire object into the structure that I need?
const convertObj = (obj) => {
return Object.entries(obj).reduce((initialVal, [name, nestedProperty]) => {
initialVal.push({ ...nestedProperty, name })
return initialVal
}, [])
}
const getNestedProperties = (data) => {
for (const key in data) {
const keyDetails = data[key]
if (keyDetails.hasOwnProperty('properties')) {
const keyProperties = keyDetails['properties']
keyDetails['properties'] = []
keyDetails['properties'].push(convertObj(keyProperties))
getNestedProperties(keyProperties)
}
}
}

If the object has a properties key, map the entries and create an array of objects with each key as name and rest of the value. Loop through the object and check if each property is an object. If yes, recursively call the function. The { ...o } part creates a copy of the input, so that it isn't mutated.
const test = {properties:{meta:{type:"object",properties:{agencyId:{type:"string",example:"e767c439-08bf-48fa-a03c-ac4a09eeee8f",description:"agencyId",},},},data:{type:"object",properties:{intervalStartTime:{type:"string",description:"Time in GMT",example:"1591702200000",},group:{type:"object",properties:{groupType:{type:"string",description:"Group Type",example:"vip",},numberofRequests:{type:"number",example:10198,description:"Amount of requests coming from group",}}}}}}};
function convert({ ...o }) {
for (const key in o) {
if (typeof o[key] === 'object')
o[key] = convert(o[key])
}
if (o.hasOwnProperty("properties"))
o.properties = Object.entries(o.properties).map(([name, v]) => ({ name, ...v }))
return o
}
console.log(convert(test))

If you have no properties property, just return the rest of the object. If you do, return the rest of the object plus a properties array property formed by taking each name-value entry in that property and converting it into an object by adding the property name to the result of calling transform on that value.
The code is fairly simple:
const transform = ({properties, ...rest} = {}) =>
properties
? {
... rest,
properties: Object .entries (properties) .map (([name, val]) => ({
name,
... transform (val)
}))
}
: {... rest}
const test = {properties: {meta: {type: 'object', properties: {agencyId: {type: 'string', example: 'e767c439-08bf-48fa-a03c-ac4a09eeee8f', description: 'agencyId'}}}, data: {type: 'object', properties: {intervalStartTime: {type: 'string', description: 'Time in GMT', example: '1591702200000'}, oup: {type: 'object', properties: {grupType: {type: 'string', description: 'Group Type', example: 'vip'}, numberofRequests: { type: 'number', example: 10198, description: 'Amount of requests coming from group'}}}}}}}
console .log (transform (test))
.as-console-wrapper {max-height: 100% !important; top: 0}

Here is a solution where object-scan does the heavy lifting. Note that this works because traversal happens in delete safe order.
// const objectScan = require('object-scan');
const test = { properties: { meta: { type: 'object', properties: { agencyId: { type: 'string', example: 'e767c439-08bf-48fa-a03c-ac4a09eeee8f', description: 'agencyId' } } }, data: { type: 'object', properties: { intervalStartTime: { type: 'string', description: 'Time in GMT', example: '1591702200000' }, group: { type: 'object', properties: { groupType: { type: 'string', description: 'Group Type', example: 'vip' }, numberofRequests: { type: 'number', example: 10198, description: 'Amount of requests coming from group' } } } } } } };
const rewrite = (obj) => objectScan(['**.properties'], {
rtn: 'count', // returns number of rewrites
filterFn: ({ value, parent, property }) => {
parent[property] = Object.entries(value)
.map(([name, v]) => ({ name, ...v }));
}
})(obj);
console.log(rewrite(test));
// => 4
console.log(test);
// => { properties: [ { name: 'meta', type: 'object', properties: [ { name: 'agencyId', type: 'string', example: 'e767c439-08bf-48fa-a03c-ac4a09eeee8f', description: 'agencyId' } ] }, { name: 'data', type: 'object', properties: [ { name: 'intervalStartTime', type: 'string', description: 'Time in GMT', example: '1591702200000' }, { name: 'group', type: 'object', properties: [ { name: 'groupType', type: 'string', description: 'Group Type', example: 'vip' }, { name: 'numberofRequests', type: 'number', example: 10198, description: 'Amount of requests coming from group' } ] } ] } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan

Related

Filter objects in array by any value within another array

I have an array of objects, and an array of values that I need to filter the objects by. I can remove duplicates from the array, but trying to figure out how to filter the objects with ids that have a match (or using startsWith()) to filter objects with an id that starts with a given value.
In the end, the object with id "F11v6" should be removed from the resulting array.
let blacklistedComponents = ["F11", "U30"];
let components = [
{ id: "F11v6", type: "unknown" },
{ id: "U30v3", type: "unknown" },
{ id: "CH11", type: "unknown" },
{ id: "CT12", type: "true" },
{ id: "U03v5", type: "unknown" },
{ id: "CT12", type: "true" }
];
console.log(components.filter((v,i,a)=>a.findIndex(v2=>(v2.id===v.id))===i));
You could have a look to the blalisted items an check if id is in the list.
const
blacklistedComponents = ["F11", "U30"],
components = [{ id: "F11v6", type: "unknown" }, { id: "U30v3", type: "unknown" }, { id: "CH11", type: "unknown" }, { id: "CT12", type: "true" }, { id: "U03v5", type: "unknown" }, { id: "CT12", type: "true" }],
result = components
.filter(({ id }) => !blacklistedComponents.some(v => id.includes(v)))
.filter((s => ({ id }) => !s.has(id) && s.add(id))(new Set));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A single loop approach
const
blacklistedComponents = ["F11", "U30"],
components = [{ id: "F11v6", type: "unknown" }, { id: "U30v3", type: "unknown" }, { id: "CH11", type: "unknown" }, { id: "CT12", type: "true" }, { id: "U03v5", type: "unknown" }, { id: "CT12", type: "true" }],
result = components.filter(
(s => ({ id }) =>
!blacklistedComponents.some(v => id.includes(v)) &&
!s.has(id) &&
s.add(id)
)(new Set)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can filter those elements that match the ones from the disallowed list using !.some(). Then apply your filter to get rid of the duplicates, e.g.:
const disallowed = ['F11', 'U30'];
const components = [
{id: 'F11v6', type: 'unknown'},
{id: 'U30v3', type: 'unknown'},
{id: 'CH11', type: 'unknown'},
{id: 'CT12', type: 'true'},
{id: 'U03v5', type: 'unknown'},
{id: 'CT12', type: 'true'}
];
const filtered = components
.filter((v) => !disallowed.some(e => v.id.startsWith(e)))
.filter((v,i,a)=>a.findIndex(v2=>(v2.id===v.id))===i);
console.log(filtered)
let blacklistedComponents = ["F11", "U30"]
let components = [
{ id: "F11v6", type: "unknown" },
{ id: "U30v3", type: "unknown" },
{ id: "CH11", type: "unknown" },
{ id: "CT12", type: "true" },
{ id: "U03v5", type: "unknown" },
{ id: "CT12", type: "true" }
]
const idAllowed = id => !blacklistedComponents.some(c=>id.startsWith(c))
const result = [...new Set(components.map(({id})=>id))] // unique ids
.filter(idAllowed) // retain only non-blacklisted ids
.map(id=>components.find(i=>id===i.id)) // correlate to components
console.log(result)
Not entirely sure if you just wanted to remove elements that start with a black listed item, or if ones that contain it anywhere.
Used composition to show how you could do either, and deconstruction to extract the relevant field as the parameter for each filter.
const disallowed = ['F11', 'U30'];
const components = [
{id: 'F11v6', type: 'unknown'},
{id: 'U30v3', type: 'unknown'},
{id: 'CH11', type: 'unknown'},
{id: 'CT12', type: 'true'},
{id: 'U03v5', type: 'unknown'},
{id: 'CT12', type: 'true'}
];
let blackListFilterContains = ( {id} ) => !disallowed.some(testValue => id.includes(testValue));
let blackListFilterStartsWith = ( {id} ) => !disallowed.some(testValue => id.startsWith(testValue));
let uniqueFilter = (value, index, self) => self.indexOf(value) === index;
let result = components.filter(blackListFilterContains);
console.log(result)
result = result.filter(uniqueFilter);
console.log(result)

generate tree-like structure using dot-separated string

I struggle with tree structure. :(
I would like to make a tree following the below interface.
export type Tree = Array<TreeNode>;
export interface TreeNode {
label: string;
type: 'folder' | 'file';
children: Array<TreeNode> | string;
}
For example,
I have a JSON file like this
{
"common.header.title": "Header Title",
"common.footer.btn": "Footer button",
"common.footer.btn.submit": "Footer Submit",
"apage.title": "apage Title"
}
My expected output is like this.
const out = [
{
label: 'apage',
type: 'folder',
children: [
{ label: 'title', type: 'file', children: 'apage Title' }
]
}, {
label: 'common',
type: 'folder',
children: [
{
label: 'footer',
type: 'folder',
children: [
{ label: 'btn', type: 'file', children: 'Footer button' },
{
label: 'btn',
type: 'folder',
children: [{ label: 'submit', type: 'file', children: 'Footer Submit' }]
},
]
}, {
label: 'header',
type: 'folder',
children: [{ label: 'title', type: 'file', children: 'Header Title' }]
}
]
}
]
I've seen some similar cases in SO, but I couldn't refer and develop with that cases.
You could search for the wanted labels and check if children is an array, then use this object for searchin in the nested array.
Finally push a new object.
const
data = { "common.header.title": "Header Title", "common.footer.btn": "Footer button", "common.footer.btn.submit": "Footer Submit", "apage.title": "apage Title" },
result = Object
.entries(data)
.sort(([a], [b]) => a.localeCompare(b))
.reduce((r, [path, children]) => {
const
keys = path.split('.'),
label = keys.pop();
keys
.reduce((t, label) => {
let temp = t.find(o => o.label === label && Array.isArray(o.children));
if (!temp) t.push(temp = { label, type: 'folder', children: [] });
return temp.children;
}, r)
.push({ label, type: 'file', children });
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Merge two objects and enhance result by adding a new key

I have two objects at the moment (one JSON Schema and one as a response from our API) which I want to merge for better mapping and usage.
The Schema looks like this:
// schema
{
key: {
description: "foo",
properties: {
values: {
title: "foo",
type: "Array"
},
type: "string"
},
type: "object"
},
foo: {
title: "title",
description: "bar"
},
bar: {
title: "title",
description: "who"
}
}
And my response object is similar to this:
// response
{
key: {
values: [0, 1]
type: "point"
},
foo: null,
bar: "some string"
}
I would simply like to merge those two objects, but using const mergedObject = {...schema, ...response} would cause overriding the values.
So my desired outcome would contain a new object prop called value or something which contains the values of the response object:
{
key: {
value: {
values: [0, 1],
type: "point",
},
description: "foo",
properties: {
values: {
title: "foo",
type: "Array"
},
type: "string"
},
type: "object"
},
foo: {
value: null,
title: "title",
description: "bar"
},
bar: {
value: "some string",
title: "title",
description: "who"
}
}
Is this doable using the spread operator? I couldn't find a decent solution here since lodashs assign or assignIn don't provide that functionality either.
I tried this function as well:
function merge (...objs) =>
[...objs].reduce(
(acc, obj) =>
Object.keys(obj).reduce((a, k) => {
acc[k] = acc.hasOwnProperty(k) ? [].concat(acc[k]).concat(obj[k]) : obj[k];
return acc;
}, {}),
{}
);
but it gives me
{
bar: [
{
title: "title",
description: "who"
},
"some string",
],
foo: [
{
title: "title",
description: "bar",
},
null
],
key: [
{
description: "foo",
properties: {
values: {
title: "foo",
type: "Array"
},
type: "string"
},
type: "object"
},
{
values: [0, 1]
type: "point"
}
]
}
which is also not what i want.
Any help is appreciated, thanks!
You can combine Object.keys(...) and Spread Operator:
const objA = {
key: {
description: "foo",
properties: {
values: {
title: "foo",
type: "Array"
},
type: "string"
},
type: "object"
},
foo: {
title: "title",
description: "bar"
},
bar: {
title: "title",
description: "who"
}
}
const objB = {
key: {
values: [0, 1],
type: "point"
},
foo: null,
bar: "some string"
}
function mergeObjects (objectA, objectB) {
const mergedObject = {};
Object.keys(objectA).forEach((key) => {
mergedObject[key] = {
...objectA[key],
value: typeof objectB[key] === 'object' && objectB[key] !== null
? { ...objectB[key] }
: objectB[key]
}
})
return mergedObject;
}
console.log(mergeObjects(objA, objB));
You need to look into this
const data = {
key: {
description: 'foo',
properties: {
values: {
title: 'foo',
type: 'Array',
},
type: 'string',
},
type: 'object',
},
foo: {
title: 'title',
description: 'bar',
},
bar: {
title: 'title',
description: 'who',
},
};
const res = {
key: {
values: [0, 1],
type: 'point',
},
foo: null,
bar: 'some string',
};
const output = { ...data };
Object.keys(res).forEach((r) => {
const isPresent = !!(data[r]);
if (isPresent) {
const responseValues = res[r];
output[r] = { responseValues, ...data[r] };
} else {
output[r] = res[r];
}
});
console.log(output);

How to get a subset of a Javascript object with nested properties?

I'm developing with Angular and I have the following Typescript array of objects:
docs = [
{
id: '1',
type: {
id: 1
desc: 'Category 1',
}
title: 'Foo",
date: '2018-06-21',
attachments: [
{ id: 51, filename: 'foo.pdf', title: 'Foo' },
{ id: 20, filename: 'bar.doc', title: 'Bar' }
]
},
{
id: '2',
type: {
id: 2
desc: 'Category 2',
}
title: 'Bar",
date: '2018-06-21',
attachments: [
{ id: 15, filename: 'foobar.xls', title: 'Foobar' },
{ id: 201, filename: 'example.doc', title: 'Example' }
]
}
]
I need to get only a subset of the properties, something like this:
docs = [
{
id: '1',
type: {
id: 1
desc: 'Category 1',
}
attachments: [
{ id: 51 },
{ id: 20 }
]
},
{
id: '2',
type: {
id: 2
desc: 'Category 2',
}
attachments: [
{ id: 15 },
{ id: 201 }
]
}
]
How can I achieve this?
Have I to create a parser or does exist any smart way (such as Lodash) to extract a lite version of the array?
var docs = [{"id":"1","type":{"id":1,"desc":"Category 1"},"title":"Foo","date":"2018-06-21","attachments":[{"id":51,"filename":"foo.pdf","title":"Foo"},{"id":20,"filename":"bar.doc","title":"Bar"}]},{"id":"2","type":{"id":2,"desc":"Category 2"},"title":"Bar","date":"2018-06-21","attachments":[{"id":15,"filename":"foobar.xls","title":"Foobar"},{"id":201,"filename":"example.doc","title":"Example"}]}];
const result = docs.map(({id,type,attachments})=>{
let doc={id,type};
doc.attachments=attachments.map(({id})=>({id}));
return doc;
});
console.log(result);
have a look at this. this works perfectly!
You can use array.map and object destructuring to extract only the wanted properties.
Also use JSON.parse and JSON.stringify to make a copy and avoid side effetcs.
docs2 = JSON.parse(JSON.stringify(docs)).map(
({id, type, attachements}) =>
({ id,
type,
attachements: attachements.map(({id}) => ({id})
})
)
You can use Array.map with object spreading, something like this:
const mapSubset = ({ id, type, attachments }) => {
return { id, type, attachments: attachments.map( {id} => id ) };
};
const subset = docs.map( mapSubset );
I was looking for a non-specific way to accomplish this or any other similar cases, so far I've thought of the following:
Have an IMapping<T> type, that defines the way to map each property.
Have an IMappingFunction<T> interface, that determines how to map a specific thing:
The following code demonstrates it:
type IMapping<T> = {
[P in keyof T]: IMapping<T[P]> | IMappingFunction<T[P]>;
}
interface IMappingFunction<T>{
(t: T): T | Partial<T>
}
class Person{
name: string;
lastName: string;
}
const obj: IMapping<Person> = {
name: s => s.toUpperCase(),
lastName: s => s
}
function map<T>(obj: T, mapping: IMapping<T>) {
return Object.keys(obj)
.map(prop => {
const propMapping = mapping[prop];
return {
key: prop,
value: typeof propMapping === 'function' ?
propMapping(obj[prop]) :
map(obj, propMapping)
};
})
.reduce((acc, prop) => ({...acc, [prop.key]: prop.value}), { });
}
console.log(map({ name: 'Name', lastName: 'LastName'}, obj));
For a runnable snippet check here
do you need to leave the original array intact? If not you can iterate through the list of objects using a for loop and use the 'delete' operator to delete the properties you no longer want.
For example:
var Employee = {
firstname: "Mohammed",
lastname: "Haddad"
}
delete Employee.firstname;
console.log(Employee);
// expected output: { lastname: "Haddad" }

How to delete particular nodes within a nested object tree in JavaScript

Here is where my algorithm skills ends. I can traverse through the object and find a certain object but I'm not able to delete the object in the same time.
Here is the object
const obj = {
children: [{
children: [
{
children: [
{
key: 'a1',
type: 'a1_type'
},
{
key: 'a2',
type: 'a2_type'
}
],
key: 'root',
type: 'root_type'
},
{
key: 'error',
type: 'error_type'
}
]
}]
}
The object with the key === 'error' object can be in any children array. I want to find it and delete the object that contains the key.
The output should be like that:
let output = findAndDeleteObjByKeyAndType('error', 'error_type')
output = {
children: [{
children: [
{
children: [
{
key: 'a1',
type: 'a1_type'
},
{
key: 'a2',
type: 'a2_type'
}
],
key: 'root',
type: 'root_type'
}
]
}]
}
Can someone help here?
Array methods like filter and every can come in handy here:
const object = {
children: [{
children: [{
children: [{
key: 'a1',
type: 'a1_type'
},
{
key: 'a2',
type: 'a2_type'
},
{
key: 'error',
type: 'error_type'
}
],
key: 'root',
type: 'root_type'
},
{
key: 'error',
type: 'error_type'
}
]
}]
}
function purgeAll (object, target) {
if (object.children) {
const keys = Object.keys(target)
object.children = object.children.filter(o =>
!keys.every(k => target[k] === o[k]) && purgeAll(o, target)
)
}
return object
}
let output = purgeAll(object, {
key: 'error',
type: 'error_type'
})
console.log(output)
.as-console-wrapper { min-height: 100%; }

Categories

Resources