generate tree-like structure using dot-separated string - javascript

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; }

Related

How to make a function as pure function using javascript and react?

i have data like below,
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
}
]
I have to find out the count of type: 'MAIN'. these 'MAIN' will be within type: "type2"
So the expected count is 6.
below is the code,
const ParentComponent = () => {
const findCount = (arr_obj) => {
let count = 0;
const expectedCount = 2;
const loop = (children) => {
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children);
} else if (type === 'MAIN') {
++count;
if (count > expectedCount) return;
}
}
};
loop(children);
return count > expectedCount;
};
const output = findCount(arr_obj);
return (
//some jsx rendering
);
}
the above code works fine. but i want to make loop(children) function a pure function. I am not sure how to do it.
the problem now is i define variables outside the loop method.
how can i define everything as arguments to the function, you could move the function outside the component.
could someone help me with this. thanks.
You could take an array of the wanted type order and iterate only one level and han over the rest of wanted type. If no type are left over, return one otherwise the result of nested count.
const
getCount = (array, types) => {
let count = 0;
for (const { type, children } of array) {
if (types[0] === type) {
count += types.length === 1
? 1
: getCount(children, types.slice(1));
}
}
return count;
}
data = [{ id: '1', children: [], type: 'TYPE1' }, { id: '2', children: [{ id: '1', children: [{}], type: 'MAIN' }, { id: '2', children: [{}], type: 'MAIN' }, { id: '3', children: [{} ], type: 'MAIN' }], type: 'TYPE2' }, { id: '3', children: [{ id: '4', children: [{}], type: 'MAIN' }, { id: '5', children: [{}], type: 'MAIN' }, { id: '6', children: [{}], type: 'MAIN' }], type: 'TYPE2' }],
order = ['TYPE2', 'MAIN'],
count = getCount(data, order);
console.log(count);
Pure Function is a function (a block of code ) that always returns the same result if the same arguments are passed. It does not depend on any state, or data change during a program’s execution rather it only depends on its input arguments.
Reference
In the above shared code I could see expectedCount as the shared variable which is not the PURE function.
As I could see your comments the desired type is in Children then its just the 2 levels. Then the following code would work.
function count(data, condition) {
let count = 0;
data.forEach((value, index) => {
if(value.type === condition[0]){
value.children.forEach((val, idx) => {
if(val.type === condition[1]) {
count++;
}
})
}
});
return count;
}
const condition = ['TYPE2', 'MAIN'];
console.log(count(arr_obj, condition));
Nina's answer is more to the point but you could also do it by filtering the input array.
const data = [{ id: '1', children: [], type: 'TYPE1' }, { id: '2', children: [{ id: '1', children: [{}], type: 'MAIN' }, { id: '2', children: [{}], type: 'MAIN' }, { id: '3', children: [{} ], type: 'MAIN' }], type: 'TYPE2' }, { id: '3', children: [{ id: '4', children: [{}], type: 'MAIN' }, { id: '5', children: [{}], type: 'MAIN' }, { id: '6', children: [{}], type: 'MAIN' }], type: 'TYPE2' }];
const count = data
.filter(v=>v.type==='TYPE2')
.flatMap(v=>v.children)
.filter(v=>v.type==='MAIN')
.length
console.log(count);

Modify structure of deeply nested object into an array of objects

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

Flatten a deeply nested array with objects and arrays

I have an array of objects that contain another array with objects. The nesting is four levels deep.
The structure of the array is:
[
{
title: 'Title',
type: 'section',
links: [
{
label: 'Label',
id: 'id_1',
links: [
{
title: 'Title',
type: 'section',
links: [
{
label: 'Label',
id: 'id_2',
links: [
{
label: 'Label',
id: 'id_3',
links: [],
}
]
}
]
},
{
title: 'Other title',
type: 'section',
links: [
{
label: 'Label',
id: 'id_4',
links: [],
}
]
}
]
}
]
}
]
I want to have a flattened array with the id's of the link arrays that contain links (they are parents of submenu's).
So the desired outcome is like:
["id_1", "id_2"]
I have tried to get the outcome with this function taken from MDN:
flatDeep(arr, d = 1) {
return d > 0
? arr.reduce((acc, val) =>
acc.concat(Array.isArray(val.links)
? this.flatDeep(val.links, d - 1)
: val.links), [])
: arr.slice();
}
This gives me an empty array.
Use Array.flatMap(). Destructure each object and use an empty array as default for missing id values. Concat the id and the result of flattening the links recursively.
const flattenIds = arr => arr.flatMap(({ id = [], links }) =>
[].concat(id, flattenIds(links))
);
const data = [{ title: 'Title', type: 'section', links: [{ label: 'Label', id: 'id_1', links: [{ title: 'Title', type: 'section', links: [{ label: 'Label', id: 'id_2', links: [{ label: 'Label', id: 'id_3', links: [] }] }] }, { title: 'Other title', type: 'section', links: [{ label: 'Label', id: 'id_4', links: [] }] }] }] }];
const result = flattenIds(data);
console.log(result);
You could get a flat array with a recursion and a check for id for missing property.
const
getId = ({ id, links }) => [
...(id === undefined ? [] : [id]),
...links.flatMap(getId)
],
data = [{ title: 'Title', type: 'section', links: [{ label: 'Label', id: 'id_1', links: [{ title: 'Title', type: 'section', links: [{ label: 'Label', id: 'id_2', links: [{ label: 'Label', id: 'id_3', links: [] }] }] }, { title: 'Other title', type: 'section', links: [{ label: 'Label', id: 'id_4', links: [] }] }] }] }],
result = data.flatMap(getId);
console.log(result);
Here is a non-recursive version.
const data = [{title:'Title',type:'section',links:[{label:'Label',id:'id_1',links:[{title:'Title',type:'section',links:[{label:'Label',id:'id_2',links:[{label:'Label',id:'id_3',links:[]}]}]},{title:'Other title',type:'section',links:[{label:'Label',id:'id_4',links:[]}]}]}]}];
const stack = data.slice();
const result = [];
let obj;
while (obj = stack.shift()) {
if ("id" in obj && obj.links.length > 0) result.push(obj.id);
stack.push(...obj.links);
}
console.log(result);
This uses breath first, but can easily be changed into depth first. You'll only have to change the stack.push call into stack.unshift.
For a more detailed explanation about the two, check out Breadth First Vs Depth First.
var array = JSON.parse('[{"title":"Title","type":"section","links":[{"label":"Label","id":"id_1","links":[{"title":"Title","type":"section","links":[{"label":"Label","id":"id_2","links":[{"label":"Label","id":"id_3","links":[]}]}]},{"title":"Other title","type":"section","links":[{"label":"Label","id":"id_4","links":[]}]}]}]}]');
arr = [];
while(array.length != 0) {
var ob1 = array.splice(0,1)[0];
for(var ob2 of ob1.links) {
if (ob2.links.length !== 0) {
arr.push(ob2.id);
array = array.concat(ob2.links);
}
}
}
console.log(arr);
Here's the output as you requested:
[
"id_1",
"id_2"
]
I think recursive function will simplify. (recursively look for lists array and push the id into res).
const data = [
{
title: "Title",
type: "section",
links: [
{
label: "Label",
id: "id_1",
links: [
{
title: "Title",
type: "section",
links: [
{
label: "Label",
id: "id_2",
links: [
{
label: "Label",
id: "id_3",
links: []
}
]
}
]
},
{
title: "Other title",
type: "section",
links: [
{
label: "Label",
id: "id_4",
links: []
}
]
}
]
}
]
}
];
const res = [];
const ids = data => {
data.forEach(item => {
if ("id" in item) {
res.push(item.id);
}
if (item.links) {
ids(item.links);
}
});
};
ids(data);
console.log(res);

Replace strings with objects in nested arrays

I have a large JS object which I feed to a factory. The original object contains strings in an array which are a parameter for the factory.
I would like to shorten the code to as short as possible and hopefully be functional.
What I have so far:
const myConfigObject = {
label: 'something',
children: [
{
id: 'one',
style: 'some style',
children: ['key1', 'key2']
},
{
id: 'two',
style: 'some other style',
children: ['key3', 'key4']
},
]
}
function DummyFactory (key) {
return {
id: key,
data: 'generated stuff'
};
}
// How to optimize this call?
myConfigObject.children.forEach(child => {
child.children = child.children.map(subChild => DummyFactory(subChild))
});
console.log(myConfigObject);
There's not much to do here, but I would go for
function DummyFactory(key) {
return {
id: key,
data: 'generated stuff'
};
}
const myConfigObject = {
label: 'something',
children: [
{
id: 'one',
style: 'some style',
children: ['key1', 'key2'].map(DummyFactory)
},
{
id: 'two',
style: 'some other style',
children: ['key3', 'key4'].map(DummyFactory)
},
]
};
Trying to shortening:
myConfigObject.children.forEach(
child => child.children = child.children.map(DummyFactory));
To achieve expected use below option of even avoiding DummyFactory
myConfigObject.children.forEach(child => {
child.children = child.children.map(subChild=> {
return {
"id":subChild,
"data": 'generated stuff'
}
})
});
code sample - https://codepen.io/nagasai/pen/VxWJYd?editors=1010
const myConfigObject = {
label: 'something',
children: [
{
id: 'one',
style: 'some style',
children: ['key1', 'key2']
},
{
id: 'two',
style: 'some other style',
children: ['key3', 'key4']
},
]
}
myConfigObject.children.forEach(child => {
child.children = child.children.map(subChild=> {
return {
"id":subChild,
"data": 'generated stuff'
}
})
});
console.log(myConfigObject);

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