Does anyone know of a way to group an array of objects by an object key then create a new array of objects based on the grouping? For example, I have an array of menu objects:
let menus = [
{id: 1, parentId: 0, text: 'Home'},
{id: 2, parentId: 0, text: 'About'},
{id: 3, parentId: 0, text: 'Product'},
{id: 4, parentId: 3, text: 'Product Category 1'},
{id: 5, parentId: 3, text: 'Product Category 2'},
{id: 6, parentId: 3, text: 'Product Category 3'},
{id: 7, parentId: 0, text: 'Contact'},
{id: 8, parentId: 7, text: 'Contact child 1'},
{id: 9, parentId: 0, text: 'Blog'},
]
I want to make a new array of menu objects that's grouped by parentId
[
{id: 1, parentId: 0, text: 'Home'},
{id: 2, parentId: 0, text: 'About'},
{id: 3, parentId: 0, text: 'Product', child: [
{id: 4, parentId: 3, text: 'Product Category 1'},
{id: 5, parentId: 3, text: 'Product Category 2'},
{id: 6, parentId: 3, text: 'Product Category 3'},
]},
{id: 7, parentId: 0, text: 'Contact', child: [
{id: 8, parentId: 7, text: 'Contact child 1'},
]},
{id: 9, parentId: 0, text: 'Blog'},
]
UPDATE this is my code:
let newListMenu = []
menus.forEach(it => {
let childrenData = []
if (it.parentId&& it.parentId!== 0) {
let parent = menus.find(_it => _it.Id == it.parentId)
childrenData.push(it)
parent.child= child
} else {
newListMenu.push(it)
}
})
You can achieve this using Array.reduce easily.
Here I have demonstrated a case where you have multi level of nesting. That is you can have child nodes inside a child node. If you have only one level of nesting you can use the commented loginc in the code for finding the parent node.
See code comments for implementation details.
const menus = [{ id: 1, parentId: 0, text: 'Home' }, { id: 2, parentId: 0, text: 'About' }, { id: 3, parentId: 0, text: 'Product' }, { id: 4, parentId: 3, text: 'Product Category 1' }, { id: 5, parentId: 3, text: 'Product Category 2' }, { id: 6, parentId: 3, text: 'Product Category 3' }, { id: 7, parentId: 0, text: 'Contact' }, { id: 8, parentId: 7, text: 'Contact child 1' }, { id: 9, parentId: 0, text: 'Blog' }, { id: 10, parentId: 6, text: 'New Node' }];
const findParentNode = (nodes, child) => {
let parent;
// Loop through input array
// Check if the id of any node is matching with the parent id
nodes.forEach((item) => {
if(item.id === child.parentId) {
parent = item;
return; // Break loop
} else if(item.child && item.child.length) {
// Check the child nodes
const newParent = findParentNode(item.child, child);
parent = newParent ? newParent : parent;
return; // Break loop
}
});
return parent;
}
const groupedMenu = menus.reduce((acc, curr) => {
// Use this if you have one level of nesting.
// const parentNode = acc.find(node => node.id === curr.parentId);
// Find the parent node from the accumulator list and the current node
const parentNode = findParentNode(acc, curr);
// If parent node is found, push the current node to the child node of parent
// Else push the node to accumulator
if (parentNode) {
parentNode.child ? parentNode.child.push(curr) : parentNode.child = [curr]
} else {
acc.push(curr);
}
return acc;
}, []);
console.log(groupedMenu);
You can use reduce.
const menus = [
{id: 1, parentId: 0, text: 'Home'},
{id: 2, parentId: 0, text: 'About'},
{id: 3, parentId: 0, text: 'Product'},
{id: 4, parentId: 3, text: 'Product Category 1'},
{id: 5, parentId: 3, text: 'Product Category 2'},
{id: 6, parentId: 3, text: 'Product Category 3'},
{id: 7, parentId: 0, text: 'Contact'},
{id: 8, parentId: 7, text: 'Contact child 1'},
{id: 9, parentId: 0, text: 'Blog'},
];
const nested = menus.reduce((acc, item) => {
// if parentId is 0, add to acc.
if (item.parentId === 0) {
return [
...acc,
item,
];
}
// else find the parent.
const parentIndex = acc.findIndex(parent => parent.id === item.parentId);
const parent = acc[parentIndex];
const children = parent.children ?? [];
// update the array item at index.
return Object.assign(
[],
acc,
{
[parentIndex]: {
...parent,
children: [...children, item], // add the item as a child.
},
},
);
}, []);
console.log(nested);
EDIT: This will only add the children on a single level. For multiple levels of nesting, you should probably do recursion.
let menus = [
{id: 1, parentId: 0, text: 'Home'},
{id: 2, parentId: 0, text: 'About'},
{id: 3, parentId: 0, text: 'Product'},
{id: 4, parentId: 3, text: 'Product Category 1'},
{id: 5, parentId: 3, text: 'Product Category 2'},
{id: 6, parentId: 3, text: 'Product Category 3'},
{id: 7, parentId: 0, text: 'Contact'},
{id: 8, parentId: 7, text: 'Contact child 1'},
{id: 9, parentId: 0, text: 'Blog'},
]
function getUniqGroupList(data, val) {
var grouped = [];
data.forEach(function (a) {
if (!this[a[val]]) {
this[a[val]] = {
parentId: a[val],
items: []
};
grouped.push(this[a[val]]);
}
this[a[val]].items.push(a);
}, Object.create(null));
return grouped;
}
var d = getUniqGroupList(menus,'parentId');
console.log(d);