Related
Unfiltered Object
I have this JSON object:
items = [
{id: 1, name: "home", parent: 0, active: 1, order: 0},
{id: 2, name: "dashboard", parent: 0, active: 1, order: 1},
{id: 3, name: "report1", parent: 2, active: 1, order: 11},
{id: 4, name: "analytics", parent: 0, active: 1, order: 2},
{id: 5, name: "report2", parent: 2, active: 1, order: 21},
{id: 6, name: "report3", parent: 2, active: 1, order: 22},
{id: 7, name: "analytics_page1", parent: 4, active: 1, order: 23}
]
Object I want
I want to filter it by parent, keeping any with a parent number of 0, and moving any values with a parent number that matches the id into its children. So I want something like this:
itemsUpdated= [
{ id: 1,
name: "home",
parent: 0,
active: 1,
order: 0,
children:[]
},
{ id: 2,
name: "dashboard",
parent: 0,
active: 1,
order: 1,
children:[
{id: 3, name: "report1", parent: 2, active: 1, order: 11, children: []},
{id: 5, name: "report2", parent: 2, active: 1, order: 21, children:[]},
{id: 6, name: "report3", parent: 2, active: 1, order: 22, children:[]}
]
},
{ id: 4,
name: "analytics",
parent: 0,
active: 1,
order: 2,
children:[
{id: 7, name: "analytics_page1", parent: 4, active: 1, order: 23, children:[]}
]
}
]
My Approach
so far I have managed to add a children key with an empty array to every item:
let itemsUpdated = items;
for(let i = 0 ; i < itemsUpdated .length; i++){
itemsUpdated [i].children = [];
}
//MY UPDATED ITEMS LOOKS LIKE THIS
updatedItems = [
{id: 1, name: "home", parent: 0, active: 1, order: 0, children:[]},
{id: 2, name: "dashboard", parent: 0, active: 1, order: 1, children:[]},
{id: 3, name: "report1", parent: 2, active: 1, order: 11, children:[]},
{id: 4, name: "analytics", parent: 0, active: 1, order: 2, children:[]},
{id: 5, name: "report2", parent: 2, active: 1, order: 21, children:[]},
{id: 6, name: "report3", parent: 2, active: 1, order: 22, children:[]},
{id: 7, name: "analytics_page1", parent: 4, active: 1, order: 23, children:[]}
]
How would I go about filtering and reducing this array down ?
This is a case for reduce(), accumulating each object into the children array of its parent indexed by id in the accumulator object. The result is the array stored in the ['0'] property of the returned object.
The advantage of this over some of the other approaches is that it doesn't employ nested loops.
(items array edited from question to include nested children: id: 7 is a child of id: 6)
const items = [
{ id: 3, name: "report1", parent: 2, active: 1, order: 11 },
{ id: 1, name: "home", parent: 0, active: 1, order: 0 },
{ id: 2, name: "dashboard", parent: 0, active: 1, order: 1 },
{ id: 4, name: "analytics", parent: 0, active: 1, order: 2 },
{ id: 5, name: "report2", parent: 2, active: 1, order: 21 },
{ id: 6, name: "report3", parent: 2, active: 1, order: 22 },
{ id: 7, name: "analytics_page1", parent: 6, active: 1, order: 23 }
]
const result = items.reduce((a, o) => {
a[o.id] = a[o.id] || [];
a[o.parent] = a[o.parent] || [];
a[o.parent].push({ ...o, children: a[o.id] });
return a;
}, {})['0'];
console.log(JSON.stringify(result, null, 2));
.as-console-wrapper { max-height: 100% !important; top: 0; }
A little more concise using logical nullish assignment (??=)
const items = [
{ id: 3, name: "report1", parent: 2, active: 1, order: 11 },
{ id: 1, name: "home", parent: 0, active: 1, order: 0 },
{ id: 2, name: "dashboard", parent: 0, active: 1, order: 1 },
{ id: 4, name: "analytics", parent: 0, active: 1, order: 2 },
{ id: 5, name: "report2", parent: 2, active: 1, order: 21 },
{ id: 6, name: "report3", parent: 2, active: 1, order: 22 },
{ id: 7, name: "analytics_page1", parent: 6, active: 1, order: 23 }
]
const result = items
.reduce((a, o) => (
(a[o.parent] ??= []).push({ ...o, children: (a[o.id] ??= []) }), a), {}
)['0'];
console.log(JSON.stringify(result, null, 2));
.as-console-wrapper { max-height: 100% !important; top: 0; }
ik, ik, im the latest to answer but this was a really fun question :D
let items = [
{id: 1, name: "home", parent: 0, active: 1, order: 0},
{id: 2, name: "dashboard", parent: 0, active: 1, order: 1},
{id: 3, name: "report1", parent: 2, active: 1, order: 11},
{id: 4, name: "analytics", parent: 0, active: 1, order: 2},
{id: 5, name: "report2", parent: 2, active: 1, order: 21},
{id: 6, name: "report3", parent: 2, active: 1, order: 22},
{id: 7, name: "analytics_page1", parent: 4, active: 1, order: 23}
]
//I assume you wont want the original items(since with reference logic.. some editing to this will be done)
let itemsUpdated = items //turn this line into 'let itemsUpdated = JSON.parse(JSON.stringify(items))' if you don't want items edited
//firstly a finder function to return elements which parents match an id n
function findMatches(n){
let arr=[]
itemsUpdated.forEach(a=>{if(a.parent==n){arr.push(a)}})
return arr
}
//now to link >:D
itemsUpdated.forEach(a=>{
a.children=[] //your for loop's equivalent :D
let matches=findMatches(a.id)
if(matches.length){
matches.forEach(b=>{a.children.push(b)})
}
})
//now to filter as the finishing touch
itemsUpdated=itemsUpdated.filter(a=>a.parent==0)
console.log(itemsUpdated)
const items = [
{ id: 1, name: 'home', parent: 0, active: 1, order: 0 },
{ id: 2, name: 'dashboard', parent: 0, active: 1, order: 1 },
{ id: 3, name: 'report1', parent: 2, active: 1, order: 11 },
{ id: 4, name: 'analytics', parent: 0, active: 1, order: 2 },
{ id: 5, name: 'report2', parent: 2, active: 1, order: 21 },
{ id: 6, name: 'report3', parent: 2, active: 1, order: 22 },
{ id: 7, name: 'analytics_page1', parent: 4, active: 1, order: 23 }
];
const itemsUpdated = items
.filter(el => !el.parent)
.map((el, idx) => {
el.children = [];
items.forEach(e => {
if (e.parent === idx+1) {
el.children.push(e);
}
});
return el;
});
console.log(itemsUpdated);
I also want to submit my trial with recursion in mind for nested children, you can see the example of id: 4
let items = [
{ id: 1, name: "home", parent: 0, active: 1, order: 0 },
{ id: 2, name: "dashboard", parent: 0, active: 1, order: 1 },
{ id: 3, name: "report1", parent: 2, active: 1, order: 11 },
{ id: 4, name: "analytics", parent: 3, active: 1, order: 2 },
{ id: 5, name: "report2", parent: 2, active: 1, order: 21 },
{ id: 6, name: "report3", parent: 2, active: 1, order: 22 },
{ id: 7, name: "analytics_page1", parent: 4, active: 1, order: 23 }
];
//adds children empty array
items.forEach((item) => {
item.children = [];
return item;
});
//filters parents on 0 level first
let newItems = items.filter((item) => item.parent === 0);
//recursion
items.forEach((item) => searchParent(newItems, item));
console.log(newItems);
function searchParent(someArray, childObject) {
someArray.forEach((parent) => {
if (parent.id === childObject.parent) {
parent.children.push(childObject);
} else {
if (parent.children) searchParent(parent.children, childObject);
}
});
}
I have all my parent children in a single array data.What i want is to add a new attribute (level) on each objects.
Given i have data as
var data = [
{
id: 1,
parent_id: 0,
name: "Child1",
},
{
id: 4,
parent_id: 1,
name: "Child11",
},
{
id: 5,
parent_id: 4,
name: "Child111",
},
{
id: 11,
parent_id: 4,
name: "Child112"
},
{
id: 13,
parent_id: 11,
name: "Child1121",
},
{
id: 21,
parent_id: 11,
name: "Child1122"
},
{
id: 22,
parent_id: 11,
name: "Child1123"
},
{
id: 24,
parent_id: 1,
name: 'Child12'
}
]
I want a child-parent relationship based on the parent_id of the children and assign a new attribute in each object of the array as level which represents the depth level of the children based on its parent.My Expected result is :
var data = [
{
id: 1,
parent_id: 0, <-------represents root
name: "Child1",
level:0 <--------level based on its parent_id
},
{
id: 4,
parent_id: 1
name: "Child11",
level:1
},
{
id: 5,
parent_id: 4,
name: "Child111",
level:2
},
{
id: 11,
parent_id: 4,
name: "Child112",
level:2
},
{
id: 13,
parent_id: 11,
name: "Child1121",
level:3
},
{
id: 21,
parent_id: 11,
name: "Child1122",
level:3
},
{
id: 22,
parent_id: 11,
name: "Child1123",
level:3
},
{
id: 24,
parent_id: 1,
name: 'Child12',
level:1
}
]
My Code
function buildTree(elements, parent_id, level = 0) {
elements.forEach(element => {
if (element['parent_id'] == parent_id) {
console.log('parent_id', parent_id);
// elements.filter(item=>item!==element);
element['level'] = level;
}
else{
buildTree(elements,parent_id,level+1);
}
})
return elements;
}
For sorted data, you could take an object for the level count and map a new data set.
var data = [{ id: 1, parent_id: 0, name: "Child1" }, { id: 4, parent_id: 1, name: "Child11" }, { id: 5, parent_id: 4, name: "Child111" }, { id: 11, parent_id: 4, name: "Child112" }, { id: 13, parent_id: 11, name: "Child1121" }, { id: 21, parent_id: 11, name: "Child1122" }, { id: 22, parent_id: 11, name: "Child1123" }, { id: 24, parent_id: 1, name: 'Child12' }],
levels = {},
result = data.map(o => ({
...o,
level: levels[o.id] = o.parent_id in levels
? levels[o.parent_id] + 1
: 0
}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try this
let parentLevel = []
data.map(parent => {
const { parent_id } = parent
if (!parentLevel.includes(parent_id)) {
parentLevel.push(parent_id);
}
})
const updatedData = data.map(parent => {
const { parent_id } = parent
parent.level = parentLevel.indexOf(parent_id)
return parent
})
console.log(updatedData);
The result is
(8) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
0: {id: 1, parent_id: 0, name: "Child1", level: 0}
1: {id: 4, parent_id: 1, name: "Child11", level: 1}
2: {id: 5, parent_id: 4, name: "Child111", level: 2}
3: {id: 11, parent_id: 4, name: "Child112", level: 2}
4: {id: 13, parent_id: 11, name: "Child1121", level: 3}
5: {id: 21, parent_id: 11, name: "Child1122", level: 3}
6: {id: 22, parent_id: 11, name: "Child1123", level: 3}
7: {id: 24, parent_id: 1, name: "Child12", level: 1}
If data is not sorted in a way that the parent is guaranteed to come before any of its children, then use a Map keyed by id values, which also gives better efficiency (no linear lookup in every iteration):
let data = [{ id: 1, parent_id: 0, name: "Child1" }, { id: 4, parent_id: 1, name: "Child11" }, { id: 5, parent_id: 4, name: "Child111" }, { id: 11, parent_id: 4, name: "Child112" }, { id: 13, parent_id: 11, name: "Child1121" }, { id: 21, parent_id: 11, name: "Child1122" }, { id: 22, parent_id: 11, name: "Child1123" }, { id: 24, parent_id: 1, name: 'Child12' }];
// optional step if you don't want to mutate the original objects in the array:
data = data.map(o => ({...o}));
const map = new Map(data.map(o => [o.id, o])).set(0, { level: -1 });
const setLevel = o => "level" in o ? o.level : (o.level = 1 + setLevel(map.get(o.parent_id)));
data.forEach(setLevel);
console.log(data);
You can omit the optional assignment when you are OK with adding the level property to the existing objects. But if you want the original data objects to remain untouched, and have newly created objects for storing the level property, then keep that line in.
This is an example:
I want to regroup arry2 according to the fields in arry1
var arry1 = [
{id: 1, parentId: 0, name: "phone"},
{id: 2, parentId: 1, name: "nick"}
];
var arry2 = [
{id: 7, parentId: 0, name: "phone_item1"},
{id: 8, parentId: 1, name: "phone_item2"},
{id: 9, parentId: 0, name: "nick_item1"},
{id: 10, parentId: 1, name: "nick_item2"}
];
let newArrys = arry1.filter((item)=>{
return leve_two.indexOf(arry2.parentId) == -1
})
I want to return a two-dimensional array:
[[
{id: 7, parentId: 0, name: "phone_item1"},
{id: 9, parentId: 0, name: "nick_item1"}
],[
{id: 8, parentId: 1, name: "phone_item2"},
{id: 10, parentId: 1, name: "nick_item2"}
]]
I tried Array.filter and so on.
Can you help me?
You can use filter() method along-with Object.values() to get the desired output:
const arr1 = [
{id: 1, parentId: 0, name: "phone", level: 0, productCount: 0},
{id: 2, parentId: 1, name: "nick", level: 0, productCount: 0}
];
const arr2 = [
{id: 7, parentId: 0, name: "phone_item1", level: 1, productCount: 0},
{id: 8, parentId: 1, name: "phone_item2", level: 1, productCount: 0},
{id: 9, parentId: 0, name: "nick_item1", level: 1, productCount: 0},
{id: 10, parentId: 1, name: "nick_item2", level: 1, productCount: 0}
];
const filterIds = arr1.map(({ parentId }) => parentId);
const arr3 = Object.values(arr2.reduce((r, c) => {
r[c.parentId] = r[c.parentId] || [];
r[c.parentId].push(c);
return r;
}, {}));
console.log(arr3);
.as-console-wrapper { max-height: 100% !important; top: 0; }
It looks just like grouping arry2 by parentId and arry1 looks useless 🤔
Use some lib for this. For example Ramda way:
const result = R.pipe(
R.groupBy(R.prop("parentId")),
R.toPairs,
R.map(R.last)
)(arry2)
I need to make something similar to treeview. It doesn't need collapsing it just needs to show some heirachy, but in a table view.
Flat data comes in from a database. I unflattened it and made a tree, but now that it's a tree, I wanted to turn it back into an array, so I can easily iterate using a for loop.
After looking at the source code of other treeviews my method was going to be like this:
From flat data from a db, unflatten:
[
{ id: 1, name: 'node1', parentId: 0 },
{ id: 2, name: 'node2', parentId: 1 },
{ id: 4, name: 'node4', parentId: 2 },
{ id: 5, name: 'node5', parentId: 2 },
{ id: 6, name: 'node6', parentId: 3 },
{ id: 3, name: 'node3', parentId: 1 },
]
The tree is now ordered and has a hierarchy (levels for indentation). Traverse the tree. I add level and children.
[
id: 1,
name: 'node1',
level: 0,
children: [
{
id: 2,
name: 'node2',
parentId: 1,
level: 1
children: [
{
id: 4,
name: 'node4',
parentId: 2,
level: 2,
children: []
},
{
id: 5,
name: 'node5',
parentId: 2,
children: []
},
]
},
{
id: 3,
name: 'node1',
parentId: 1,
children: [
{
id: 6,
name: 'node6',
parentId: 3,
children: []
},
]
},
]
]
Compress it back into an array form with order, level.
[
{ id: 1, name: 'node1', level: 0, parentId: 0, children: [...] },
{ id: 2, name: 'node2', level: 1, parentId: 1, children: [...] },
{ id: 4, name: 'node4', level: 2, parentId: 2, children: [...] },
{ id: 5, name: 'node5', level: 2, parentId: 2, children: [...] },
{ id: 3, name: 'node3', level: 1, parentId: 1, children: [...] },
{ id: 6, name: 'node6', level: 2, parentId: 3, children: [...] },
]
Of which I can easily create a table from.
I've gotten close with the following code:
var data = [
{ id: 1, name: 'node1', parentId: 0 },
{ id: 2, name: 'node2', parentId: 1 },
{ id: 4, name: 'node4', parentId: 2 },
{ id: 5, name: 'node5', parentId: 2 },
{ id: 6, name: 'node6', parentId: 3 },
{ id: 3, name: 'node3', parentId: 1 }
]
function unflatten (arr, parentId, level) {
let output = []
for (const obj of arr) {
if (obj.parentId === parentId) {
var children = unflatten(arr, obj.id, level+1)
obj.level = level
if (children.length) {
obj.children = children
}
output.push(obj)
}
}
// console.log(output)
return output
}
function flatten (tree) {
var output = []
for(const node of tree) {
if(node.children !== undefined){
var nodeChildren = flatten(node.children.reverse())
for(const child of nodeChildren){
output.push(child)
}
}
output.push(node)
}
return output
}
var dataCopy = Object.assign([], data)
console.log('data', dataCopy)
var res = unflatten(data, 0, 0)
console.log('tree', res)
var resCopy = Object.assign([], res)
var res2 = flatten(resCopy)
console.log('reflatten', res2)
Fiddle http://jsfiddle.net/ctd09r85/10/
That fiddle is the closest I've gotten, but it's a bit reversed and out of order.
How can I do this, and is this a reasonable way to build the tree view.
I have an array of objects that has information of nested data, and I want to convert the data to actual nested array data.
How can I convert this:
const data = [
{id: 1, parent_id: null, name: 'test1'},
{id: 2, parent_id: null, name: 'test2'},
{id: 3, parent_id: 2, name: 'test3'},
{id: 4, parent_id: 2, name: 'test4'},
{id: 5, parent_id: 4, name: 'test5'},
{id: 6, parent_id: 4, name: 'test5'},
{id: 7, parent_id: 2, name: 'test5'},
{id: 8, parent_id: 2, name: 'test5'},
{id: 9, parent_id: null, name: 'test5'},
{id: 10, parent_id: null, name: 'test5'},
]
to this:
const data = [
{id: 1, parent_id: null, name: 'test1'},
{
id: 2,
parent_id: null,
name: 'test2',
children: [
{id: 3, parent_id: 2, name: 'test3'},
{
id: 4,
parent_id: 2,
name: 'test4',
children: [
{id: 5, parent_id: 4, name: 'test5'},
{id: 6, parent_id: 4, name: 'test5'}
]
},
{id: 7, parent_id: 2, name: 'test5'},
{id: 8, parent_id: 2, name: 'test5'},
]
},
{id: 9, parent_id: null, name: 'test5'},
{id: 10, parent_id: null, name: 'test5'},
]
What is the best way to do this?
You could create recursive function with reduce method for this.
const data = [{id: 1, parent_id: null, name: 'test1'},{id: 2, parent_id: null, name: 'test2'},{id: 3, parent_id: 2, name: 'test3'},{id: 4, parent_id: 2, name: 'test4'},{id: 5, parent_id: 4, name: 'test5'},{id: 6, parent_id: 4, name: 'test5'},{id: 7, parent_id: 2, name: 'test5'},{id: 8, parent_id: 2, name: 'test5'},{id: 9, parent_id: null, name: 'test5'},{id: 10, parent_id: null, name: 'test5'},]
function nest(data, parentId = null) {
return data.reduce((r, e) => {
let obj = Object.assign({}, e)
if (parentId == e.parent_id) {
let children = nest(data, e.id)
if (children.length) obj.children = children
r.push(obj)
}
return r;
}, [])
}
console.log(nest(data))
You could take a single loop approach by using an object and the id and parent_id as key and collect the items/children to it.
The order is only important for the order in the children array.
const
data = [{ id: 1, parent_id: null, name: 'test1' }, { id: 2, parent_id: null, name: 'test2' }, { id: 3, parent_id: 2, name: 'test3' }, { id: 4, parent_id: 2, name: 'test4' }, { id: 5, parent_id: 4, name: 'test5' }, { id: 6, parent_id: 4, name: 'test5' }, { id: 7, parent_id: 2, name: 'test5' }, { id: 8, parent_id: 2, name: 'test5' }, { id: 9, parent_id: null, name: 'test5' }, { id: 10, parent_id: null, name: 'test5' }],
tree = function (data, root) {
var t = {};
data.forEach(o => {
Object.assign(t[o.id] = t[o.id] || {}, o);
t[o.parent_id] = t[o.parent_id] || {};
t[o.parent_id].children = t[o.parent_id].children || [];
t[o.parent_id].children.push(t[o.id]);
});
return t[root].children;
}(data, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This is an interesting problem. One option if you want to keep linear time at the expense of some space it to make a lookup object based on id. Then you can loop through those values and push into either a parent object or the array:
const data = [{id: 1, parent_id: null, name: 'test1'},{id: 2, parent_id: null, name: 'test2'},{id: 3, parent_id: 2, name: 'test3'},{id: 4, parent_id: 2, name: 'test4'},{id: 5, parent_id: 4, name: 'test5'},{id: 6, parent_id: 4, name: 'test5'},{id: 7, parent_id: 2, name: 'test5'},{id: 8, parent_id: 2, name: 'test5'},{id: 9, parent_id: null, name: 'test5'},{id: 10, parent_id: null, name: 'test5'},]
let lookup = data.reduce((obj, item) => {
obj[item.id] = item
return obj
}, {})
let arr = Object.values(lookup).reduce((arr, val) =>{
if (val.parent_id == null) arr.push(val)
else (lookup[val.parent_id].children || ( lookup[val.parent_id].children = [])).push(val)
return arr
}, [])
console.log(JSON.stringify(arr, null, 2))
you could try this recursive approach
const data = [{id: 1, parent_id: null, name: 'test1'}, {id: 2, parent_id: null, name: 'test2'}, {id: 3, parent_id: 2, name: 'test3'}, {id: 4, parent_id: 2, name: 'test4'}, {id: 5, parent_id: 4, name: 'test5'}, {id: 6, parent_id: 4, name: 'test5'}, {id: 7, parent_id: 2, name: 'test5'}, {id: 8, parent_id: 2, name: 'test5'}, {id: 9, parent_id: null, name: 'test5'}, {id: 10, parent_id: null, name: 'test5'}];
const transform = arr => {
return arr.reduce((acc, elem) => {
const children = data.filter(el => el.parent_id === elem.id),
isPresent = findDeep(acc, elem);
if(!isPresent && children.length)
acc.push({...elem, children: transform(children)});
else if(!isPresent)
acc.push(elem);
return acc;
}, []);
}
const findDeep =(arr = [], elem) => (
arr.some(el => (el.id === elem.id) || findDeep(el.children, elem))
);
console.log(transform(data));
const data = [
{id: 1, parent_id: null, name: 'test1'},
{id: 2, parent_id: null, name: 'test2'},
{id: 3, parent_id: 2, name: 'test3'},
{id: 4, parent_id: 2, name: 'test4'},
{id: 5, parent_id: 4, name: 'test5'},
{id: 6, parent_id: 4, name: 'test5'},
{id: 7, parent_id: 2, name: 'test5'},
{id: 8, parent_id: 2, name: 'test5'},
{id: 9, parent_id: null, name: 'test5'},
{id: 10, parent_id: null, name: 'test5'},
]
const output = data.filter(
item => !item.parent_id
).map(
rootItem => ({
...rootItem,
children: data.filter(item => item.parent_id === rootItem.id),
})
)
console.log(output)