Generate flat array of objects from dynamic nested array - javascript
I have array of objects like below. Here children element may deeply nested, it may contain other children element as well.
let response = [{
id: 4321,
name: 'Education',
parentId: null,
children: [{
id: 1234,
name: 'category1',
parentId: 4321,
children: [{
id: 8327548,
name: '001',
parentId: 1234,
}, {
id: 8327549,
name: '002',
parentId: 1234,
}],
}, {
id: 6786,
name: 'Associations',
parentId: 4321,
}, {
id: 8262439,
name: 'category1',
parentId: 4321,
}, {
id: 8245,
name: 'Rights',
parentId: 4321,
children: [{
id: 2447,
name: 'Organizations',
parentId: 8245,
}, {
id: 9525,
name: 'Services',
parentId: 8245,
}, {
id: 8448,
name: 'Organizations',
parentId: 8245,
}],
}, {
id: 8262446,
name: 'Women\'s Rights',
parentId: 4321,
}],
}, {
id: 21610,
name: 'Agriculture',
parentId: null,
children: [{
id: 3302,
name: 'categoryABC',
parentId: 21610,
children: [{
id: 85379,
name: 'categoryABC - General',
parentId: 3302,
}, {
id: 85380,
name: 'categoryABC Technology',
parentId: 3302,
}],
}, {
id: 8303,
name: 'Fungicides',
parentId: 21610,
children: [{
id: 8503,
name: 'Fungicides - General',
parentId: 8303,
}],
}],
}];
I want to make it flat array of objects but I want to add parent name (which parentId is null) to every objects inside that respective parent.
expected output:
[
{
id: 8327548,
name: "001",
parentId: 1234
mainParent: "Education"
}, {
id: 8327549,
name: "002",
parentId: 1234,
mainParent: "Agriculture"
},
// ...OTHER OBJECTS....
]
What I have done so far
function flat(array) {
var result = [];
array.forEach(function (a) {
result.push(a);
if (Array.isArray(a.children)) {
result = result.concat(flat(a.children));
delete a.children;
}
});
return result;
}
It's giving the flat array of objects but I'm not able to add parent name property to every objects.
Can someone please help me?
You could take a recursive approach an handover the first found name as mainParent.
const
flat = mainParent => o => o.children
? o.children.flatMap(flat(mainParent || o.name))
: { ...o, mainParent },
response = [{ id: 4321, name: "Education", parentId: null, children: [{ id: 1234, name: "category1", parentId: 4321, children: [{ id: 8327548, name: "001", parentId: 1234 }, { id: 8327549, name: "002", parentId: 1234 }] }, { id: 6786, name: "Associations", parentId: 4321 }, { id: 8262439, name: "category1", parentId: 4321 }, { id: 8245, name: "Rights", parentId: 4321, children: [{ id: 2447, name: "Organizations", parentId: 8245 }, { id: 9525, name: "Services", parentId: 8245 }, { id: 8448, name: "Organizations", parentId: 8245 }] }, { id: 8262446, name: "Women's Rights", parentId: 4321 }] }, { id: 21610, name: "Agriculture", parentId: null, children: [{ id: 3302, name: "categoryABC", parentId: 21610, children: [{ id: 85379, name: "categoryABC - General", parentId: 3302 }, { id: 85380, name: "categoryABC Technology", parentId: 3302 }] }, { id: 8303, name: "Fungicides", parentId: 21610, children: [{ id: 8503, name: "Fungicides - General", parentId: 8303 }] }] }],
result = response.flatMap(flat());
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This is a fairly clean recursion, stopping when there are no children in the node. We capture the first name value found along the hierarchy and carry it through.
const flat = (xs, name = null) =>
xs .flatMap (x => x .children
? flat (x .children, name || x .name)
: [{...x, mainParent: name}]
)
const response = [{id: 4321, name: "Education", parentId: null, children: [{id: 1234, name: "category1", parentId: 4321, children: [{id: 8327548, name: "001", parentId: 1234}, {id: 8327549, name: "002", parentId: 1234}]}, {id: 6786, name: "Associations", parentId: 4321}, {id: 8262439, name: "category1", parentId: 4321}, {id: 8245, name: "Rights", parentId: 4321, children: [{id: 2447, name: "Organizations", parentId: 8245}, {id: 9525, name: "Services", parentId: 8245}, {id: 8448, name: "Organizations", parentId: 8245}]}, {id: 8262446, name: "Women's Rights", parentId: 4321}]}, {id: 21610, name: "Agriculture", parentId: null, children: [{id: 3302, name: "categoryABC", parentId: 21610, children: [{id: 85379, name: "categoryABC - General", parentId: 3302}, {id: 85380, name: "categoryABC Technology", parentId: 3302}]}, {id: 8303, name: "Fungicides", parentId: 21610, children: [{id: 8503, name: "Fungicides - General", parentId: 8303}]}]}]
console .log (flat (response))
.as-console-wrapper {max-height: 100% !important; top: 0}
Less obscure, but still fairly concise.
const response = [{ id: 4321, name: 'Education', parentId: null, children: [{ id: 1234, name: 'category1', parentId: 4321, children: [{ id: 8327548, name: '001', parentId: 1234 }, { id: 8327549, name: '002', parentId: 1234 }] }, { id: 6786, name: 'Associations', parentId: 4321 }, { id: 8262439, name: 'category1', parentId: 4321 }, { id: 8245, name: 'Rights', parentId: 4321, children: [{ id: 2447, name: 'Organizations', parentId: 8245 }, { id: 9525, name: 'Services', parentId: 8245 }, { id: 8448, name: 'Organizations', parentId: 8245 }] }, { id: 8262446, name: 'Women\'s Rights', parentId: 4321 }] }, { id: 21610, name: 'Agriculture', parentId: null, children: [{ id: 3302, name: 'categoryABC', parentId: 21610, children: [{ id: 85379, name: 'categoryABC - General', parentId: 3302 }, { id: 85380, name: 'categoryABC Technology', parentId: 3302 }] }, { id: 8303, name: 'Fungicides', parentId: 21610, children: [{ id: 8503, name: 'Fungicides - General', parentId: 8303 }] }] }];
function flatten(arr, mainParent = null) {
if (!arr) return;
let result = [];
for (const obj of arr) {
result.push(...(flatten(obj.children, mainParent ?? obj.name) ?? [{ ...obj, mainParent }]));
}
return result;
}
console.log(flatten(response));
.as-console-wrapper { max-height: 100% !important; top: 0; }
A possible solution could be based entirely on a single, though recursively implemented, reduce task, where the accumulating and recursively passed down object carries both information, the mainParent value and the recursively collected final result ...
function recursivelyReassambleAndCollectChildlessItems(
{ mainParent, result }, { children, ...item }
) {
result = result.concat(children && children.reduce(
recursivelyReassambleAndCollectChildlessItems, {
// was:
// mainParent: mainParent ?? item.name,
/**
* OP quote:
* "... but I want to add parent name
* (which parentId is null) to every
* objects inside that respective parent."
*/
mainParent: item.parentId === null ? item.name : mainParent,
result: [],
}
).result || { mainParent, ...item });
return { mainParent, result };
}
const response = [{
id: 4321,
name: 'Education',
parentId: null,
children: [{
id: 1234,
name: 'category1',
parentId: 4321,
children: [{
id: 8327548,
name: '001',
parentId: 1234,
}, {
id: 8327549,
name: '002',
parentId: 1234,
}],
}, {
id: 6786,
name: 'Associations',
parentId: 4321,
}, {
id: 8262439,
name: 'category1',
parentId: 4321,
}, {
id: 8245,
name: 'Rights',
parentId: 4321,
children: [{
id: 2447,
name: 'Organizations',
parentId: 8245,
}, {
id: 9525,
name: 'Services',
parentId: 8245,
}, {
id: 8448,
name: 'Organizations',
parentId: 8245,
}],
}, {
id: 8262446,
name: 'Women\'s Rights',
parentId: 4321,
}],
}, {
id: 21610,
name: 'Agriculture',
parentId: null,
children: [{
id: 3302,
name: 'categoryABC',
parentId: 21610,
children: [{
id: 85379,
name: 'categoryABC - General',
parentId: 3302,
}, {
id: 85380,
name: 'categoryABC Technology',
parentId: 3302,
}],
}, {
id: 8303,
name: 'Fungicides',
parentId: 21610,
children: [{
id: 8503,
name: 'Fungicides - General',
parentId: 8303,
}],
}],
}];
console.log(response
.reduce(recursivelyReassambleAndCollectChildlessItems, { result: [] })
.result
)
.as-console-wrapper { min-height: 100%!important; top: 0; }
Related
How to reduce an array of objects to the required format?
I have a problem with proper grouping of objects in an array. I have an array in this format: const places = [ { id: 1, name: "p 1", icon: "icon-p", parent_id: null, }, { id: 2, name: "p 2", icon: "icon-p", parent_id: 1, }, { id: 3, name: "p 3", icon: "icon-p", parent_id: 1, }, { id: 4, name: "t 1", icon: "icon-t", parent_id: null, }, { id: 5, name: "t 2", icon: "icon-t", parent_id: 4, }, { id: 6, name: "t 3", icon: "icon-t", parent_id: 4, }, { id: 7, name: "a 1", icon: "icon-a", parent_id: null, }, { id: 8, name: "b 1", icon: "icon-b", parent_id: null, }, { id: 9, name: "h 1", icon: "icon-h", parent_id: null, }, { id: 11, name: "h 2", icon: "icon-h", parent_id: 9, }, { id: 12, name: "h 3", icon: "icon-h", parent_id: 9, }, { id: 13, name: "h 4", icon: "icon-h", parent_id: 9, }, ]; const groupReultsByIcon = places.reduce((r, { icon, ...object }) => { let temp = r.find((o) => o.icon === icon); if (!temp) r.push((temp = { icon, children: [] })); temp.children.push(object); return r; }, []); groupReultsByIcon.forEach(({ icon, children }) => { console.log('icon-name: ', icon); console.log('children-array: ', children); }); The code that I have managed to prepare so far is, unfortunately, only grouping by icon. I am trying to get such an array: const newArrayObject = [ { id: 1, icon: "icon-p", children: [ { id: 1, name: "p 1", icon: "icon-p", parent_id: null, }, { id: 2, name: "p 2", icon: "icon-p", parent_id: 1, }, { id: 3, name: "p 3", icon: "icon-p", parent_id: 1, }, ], }, { id: 4, icon: "icon-t", children: [ { id: 4, name: "t 1", icon: "icon-t", parent_id: null, }, { id: 5, name: "t 2", icon: "icon-t", parent_id: 4, }, { id: 6, name: "t 3", icon: "icon-t", parent_id: 4, }, ], }, { id: 7, icon: "icon-a", children: [ { id: 7, name: "a 1", icon: "icon-a", parent_id: null, }, ], }, { id: 8, icon: "icon-b", children: [ { id: 8, name: "b 1", icon: "icon-b", parent_id: null, }, ], }, { id: 9, icon: "icon-h", children: [ { id: 9, name: "h 1", icon: "icon-h", parent_id: null, }, { id: 11, name: "h 2", icon: "icon-h", parent_id: 9, }, { id: 12, name: "h 3", icon: "icon-h", parent_id: 9, }, { id: 13, name: "h 4", icon: "icon-h", parent_id: 9, }, ], }, ]; As you can see grouping should follow parent_id and id An object that parent_id === null should have children and in it itself as well as objects with its id.
Using Array.reduce(), it can be done as follows: const places = [ { id: 1, name: "p 1", icon: "icon-p", parent_id: null, }, { id: 2, name: "p 2", icon: "icon-p", parent_id: 1, }, { id: 3, name: "p 3", icon: "icon-p", parent_id: 1, }, { id: 4, name: "t 1", icon: "icon-t", parent_id: null, }, { id: 5, name: "t 2", icon: "icon-t", parent_id: 4, }, { id: 6, name: "t 3", icon: "icon-t", parent_id: 4, }, { id: 7, name: "a 1", icon: "icon-a", parent_id: null, }, { id: 8, name: "b 1", icon: "icon-b", parent_id: null, }, { id: 9, name: "h 1", icon: "icon-h", parent_id: null, }, { id: 11, name: "h 2", icon: "icon-h", parent_id: 9, }, { id: 12, name: "h 3", icon: "icon-h", parent_id: 9, }, { id: 13, name: "h 4", icon: "icon-h", parent_id: 9, }, ]; const result = places.reduce((prev, curr) => { const parent = prev.find(p => p.id === curr.parent_id); if (parent) { if (!parent.children) { parent.children = []; } parent.children.push(curr); } else { prev.push(curr); } return prev; }, []); console.log(result);
You can check for children in one level and then add to it. This will check only for one nested level. You can make it recursive if you want. Try like below const places = [ { id: 1, name: "p 1", icon: "icon-p", parent_id: null, }, { id: 2, name: "p 2", icon: "icon-p", parent_id: 1, }, { id: 3, name: "p 3", icon: "icon-p", parent_id: 1, }, { id: 4, name: "t 1", icon: "icon-t", parent_id: null, }, { id: 5, name: "t 2", icon: "icon-t", parent_id: 4, }, { id: 6, name: "t 3", icon: "icon-t", parent_id: 4, }, { id: 7, name: "a 1", icon: "icon-a", parent_id: null, }, { id: 8, name: "b 1", icon: "icon-b", parent_id: null, }, { id: 9, name: "h 1", icon: "icon-h", parent_id: null, }, { id: 11, name: "h 2", icon: "icon-h", parent_id: 9, }, { id: 12, name: "h 3", icon: "icon-h", parent_id: 9, }, { id: 13, name: "h 4", icon: "icon-h", parent_id: 9, }, ]; const output = places.reduce((prev, curr) => { if (curr.parent_id === null) { prev.push(curr); } else { const parent = prev.find(({ id }) => id === curr.parent_id); if (parent) { parent.children ? parent.children.push(curr) : (parent.children = [{ ...parent }, curr]); } } return prev; }, []); console.log(JSON.stringify(output, null, 2));
You can easily achieve the result using Map as: const places = [ { id: 1, name: 'p 1', icon: 'icon-p', parent_id: null, }, { id: 2, name: 'p 2', icon: 'icon-p', parent_id: 1, }, { id: 3, name: 'p 3', icon: 'icon-p', parent_id: 1, }, { id: 4, name: 't 1', icon: 'icon-t', parent_id: null, }, { id: 5, name: 't 2', icon: 'icon-t', parent_id: 4, }, { id: 6, name: 't 3', icon: 'icon-t', parent_id: 4, }, { id: 7, name: 'a 1', icon: 'icon-a', parent_id: null, }, { id: 8, name: 'b 1', icon: 'icon-b', parent_id: null, }, { id: 9, name: 'h 1', icon: 'icon-h', parent_id: null, }, { id: 11, name: 'h 2', icon: 'icon-h', parent_id: 9, }, { id: 12, name: 'h 3', icon: 'icon-h', parent_id: 9, }, { id: 13, name: 'h 4', icon: 'icon-h', parent_id: 9, }, ]; const mapObj = places.reduce((acc, curr) => { const { id, name, icon, parent_id } = curr; if (!acc.get(parent_id)) { acc.set(id, { id, icon, children: [curr], }); } else acc.get(parent_id)?.children?.push(curr); return acc; }, new Map()); const result = [...mapObj.values()]; console.log(result);
Group nested object using lodash
I have object like this. data = [ { id: "0", name: "maths", levelNo: 0, level: null }, { id: "15", name: "sceince", levelNo: 0, level: null }, { name: "algebra", id: "1", parentId: "0", levelNo: 1, level: { id: "0", name: "maths" } }, { name: "alfunction", id: "2", parentId: "1", levelNo: 2, level: { id: "1", name: "alegera" } }, { name: "bhumiti", id: "3", parentId: "1", levelNo: 2, level: { id: "1", name: "alegera" } }, { name: "paryavan", id: "4", parentId: "0", levelNo: 1, level: { id: "0", name: "maths" } }, { name: "trikon", id: "5", parentId: "3", levelNo: 3, level: { id: "3", name: "bhumiti" } }]; and convert this object into subject = [ { name: "maths", id: "0", items: [ { id: "1", name: "alegera", items: [ { name: "alfunction", id: "2" }, { name: "bhumiti", id: "3", items: [ { name: "trikon", id: "5" } ] } ] }, { id: "4", name: "paryavan" } ] }];
You could take a function which uses parentId and id without levelNo and level. const getTree = (data, root) => { const t = {}; data.forEach(({ parentId, levelNo, level, ...o }) => ((t[parentId] ??= {}).children ??= []).push(Object.assign(t[o.id] ??= {}, o)) ); return t[root].children; }, data = [{ id: "0", name: "maths", levelNo: 0, level: null }, { id: "15", name: "sceince", levelNo: 0, level: null }, { name: "algebra", id: "1", parentId: "0", levelNo: 1, level: { id: "0", name: "maths" } }, { name: "alfunction", id: "2", parentId: "1", levelNo: 2, level: { id: "1", name: "alegera" } }, { name: "bhumiti", id: "3", parentId: "1", levelNo: 2, level: { id: "1", name: "alegera" } }, { name: "paryavan", id: "4", parentId: "0", levelNo: 1, level: { id: "0", name: "maths" } }, { name: "trikon", id: "5", parentId: "3", levelNo: 3, level: { id: "3", name: "bhumiti" } }], tree = getTree(data); console.log(tree); .as-console-wrapper { max-height: 100% !important; top: 0; }
Change object structure Javascript
I have an array and I want to override the object attributes This the main data const Data = { "id": "1", "name": "USA", "questions": [{ id: 1, name: "1 qst" }, { id: 2, name: "2 qst" }, { id: 3, name: "3 qst" }], "children": [ { "id": "1" , "name": "DC" ,"questions": [{ id: 1, name: "1 qst" }, { id: 2, name: "2 qst" }, { id:2, name: "3 qst" }]}, { "id": "2" , "name": "Florida" ,"questions": [{ id: 1, name: "1 qst" }, { id: 2, name: "2 qst" }, { id: 3, name: "3 qst" }]} ] } I want to change in every question instead of name I want to put questionName like this { id: 1, questionName: "1 qst" } I was able to change it in first object question through this code let dataFiltred = Data[0]?.questions?.map((item) => { return { questionName: item.name, id: item.id, } }) But I am struggling to change it in children question
function mapQuestionObject({ name, id }) { return { id, questionName: name }; } const mapped = { ...Data, questions: Data.questions.map(mapQuestionObject), children: Data.children.map(child => ({ ...child, questions: child.questions.map(mapQuestionObject), }), };
Map each questions array to a new array and change the name property in the mapped value. const data = { "id": "1", "name": "USA", "questions": [{ id: 1, name: "1 qst" }, { id: 2, name: "2 qst" }, { id: 3, name: "3 qst" }], "children": [ { "id": "1" , "name": "DC" ,"questions": [{ id: 1, name: "1 qst" }, { id: 2, name: "2 qst" }, { id:2, name: "3 qst" }]}, { "id": "2" , "name": "Florida" ,"questions": [{ id: 1, name: "1 qst" }, { id: 2, name: "2 qst" }, { id: 3, name: "3 qst" }]} ] }; const newData = { ...data, questions: data.questions.map(({ name: questionName, ...rest }) => ({ ...rest, questionName, })), children: data.children.map(child => ({ ...child, questions: child.questions.map(({ name: questionName, ...rest }) => ({ ...rest, questionName, })) })), }; console.log(newData); Since the questions mapping is the same callback you can factor it out to make your code more DRY const data = { "id": "1", "name": "USA", "questions": [{ id: 1, name: "1 qst" }, { id: 2, name: "2 qst" }, { id: 3, name: "3 qst" }], "children": [ { "id": "1" , "name": "DC" ,"questions": [{ id: 1, name: "1 qst" }, { id: 2, name: "2 qst" }, { id:2, name: "3 qst" }]}, { "id": "2" , "name": "Florida" ,"questions": [{ id: 1, name: "1 qst" }, { id: 2, name: "2 qst" }, { id: 3, name: "3 qst" }]} ] }; const mapQuestions = arr => arr.map(({ name: questionName, ...rest }) => ({ ...rest, questionName, })); const newData = { ...data, questions: mapQuestions(data.questions), children: data.children.map(child => ({ ...child, questions: mapQuestions(child.questions), })), }; console.log(newData);
How to print json object array to print all the values as category and subcategory
I have a json object which contains categories and sub-categories. I need printed in a particular way as shown. The following should be the output of the array. I need to get all final values output like: TV Mobile Sumsung Means Tshirt This is my JSON object: var cat = { categoryList: [ { _id: "60efd92451be2c18d82f4b2a", name: "TV", slug: "TV", children: [], }, { _id: "60f018cf12ea421c403ef801", name: "Mobile", slug: "Mobile", children: [ { _id: "60f036fb2201d117c84d9667", name: "Sumsung", slug: "Sumsung", parentId: "60f018cf12ea421c403ef801", children: [], }, ], }, { _id: "60f01da22201d117c84d9665", name: "Means", slug: "Means", children: [ { _id: "60f035af2201d117c84d9666", name: "Tshirt", slug: "Tshirt", parentId: "60f01da22201d117c84d9665", children: [], }, ], }, ], };
I wrote this recursive function which takes cat object and returns an array of all the categories. function categoryExtractor(obj) { const allCategories: string[] = []; (function extractor(arr) { for (let i = 0; i < arr.length; i++) { allCategories.push(arr[i].name); if (arr[i].children) extractor(arr[i].children); } })(obj.categoryList); return allCategories; } console.log(categoryExtractor(cat));
Let me know if you need any fancy formatting for child categories while printing to console. var cat = { categoryList: [{ _id: "60efd92451be2c18d82f4b2a", name: "TV", slug: "TV", children: [], }, { _id: "60f018cf12ea421c403ef801", name: "Mobile", slug: "Mobile", children: [{ _id: "60f036fb2201d117c84d9667", name: "Sumsung", slug: "Sumsung", parentId: "60f018cf12ea421c403ef801", children: [], }, ], }, { _id: "60f01da22201d117c84d9665", name: "Means", slug: "Means", children: [{ _id: "60f035af2201d117c84d9666", name: "Tshirt", slug: "Tshirt", parentId: "60f01da22201d117c84d9665", children: [], }, ], }, ], }; function printCategory(categories) { for (category of categories) { console.log(category.name); if (category.children) printCategory(category.children); } } printCategory(cat.categoryList);
You can use Array#reduce: var cat = { "categoryList": [{ "_id": "60efd92451be2c18d82f4b2a", "name": "TV", "slug": "TV", "children": [] }, { "_id": "60f018cf12ea421c403ef801", "name": "Mobile", "slug": "Mobile", "children": [{ "_id": "60f036fb2201d117c84d9667", "name": "Sumsung", "slug": "Sumsung", "parentId": "60f018cf12ea421c403ef801", "children": [] }] }, { "_id": "60f01da22201d117c84d9665", "name": "Means", "slug": "Means", "children": [{ "_id": "60f035af2201d117c84d9666", "name": "Tshirt", "slug": "Tshirt", "parentId": "60f01da22201d117c84d9665", "children": [] }] } ] }; const values = cat.categoryList.reduce((a, e) => a.concat([e.name, e.children.map(e => e.name)]).flat(), []); console.log(values);
kendoTreeList - aggregate not working properly
Please help me, I am stuck with aggregates for kendoTreeList - I create kendoTreeList, but failed to calculate sum for groups. $(document).ready(function () { var _jsondata = [ { ID: 1, Name: "Parent 1", Amount: "200", parentId: null }, { ID: 2, Name: "Parent 2", Amount: "500", parentId: null }, { ID: 11, Name: "Child 11", Amount: "50", parentId: 1 }, { ID: 12, Name: "Child 12", Amount: "150", parentId: 1 }, { ID: 21, Name: "Child 21", Amount: "100", parentId: 2 }, { ID: 22, Name: "Child 22", Amount: "200", parentId: 2 }, { ID: 23, Name: "Child 23", Amount: "200", parentId: 2 } ]; var dataSource = new kendo.data.TreeListDataSource({ data : _jsondata, schema : { model: { id: "ID", expanded: true }}, aggregate : [ { field: "Amount", aggregate: "sum"} ] }); $("#treelist").kendoTreeList({ dataSource : dataSource, columns : [ { field: "Name" , nullable: false }, { field: "Amount", footerTemplate: "#= sum #"} ] }); }); this give result as -
Since Amount represented in _jsondata as string, you need to define it in schema.model as number schema: { model: { id: "ID", expanded: true, fields: { Amount: { type: 'number' } } } },