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)
Related
This question already has answers here:
Filter array of objects based on another array in javascript
(10 answers)
Closed last month.
I'm trying to filter an array using 2 criteria :
one is straigthforward (==1) and the other is an array.
In the below example, i would want to filter :
level = 0 or name comprises in ['B','S']
[
{id: 1, level: 0, name: 'A'},
{id: 2, level: 1, name: 'B'},
{id: 3, level: 1, name: 'S'},
{id: 4, level: 0, name: 'A'},
{id: 5, level: 0, name: 'S'},
{id: 6, level: 1, name: 'A'},
{id: 7, level: 0, name: 'B'}, ]
so the result would be :
[
{id: 1, level: 0, name: 'A'},
{id: 2, level: 1, name: 'B'},
{id: 3, level: 1, name: 'S'},
{id: 4, level: 0, name: 'A'},
{id: 5, level: 0, name: 'S'},
{id: 7, level: 0, name: 'B'}, ]
As i'm limited with ecmaScript-5 i cannot use .includes that would have helped here.
Also, i would like to stick to .filter function.
Any idea how to achieve this ?
thanks
Using ecmaScript-5 you can do:
var arr = [{ id: 1, level: 0, name: 'A' },{ id: 2, level: 1, name: 'B' },{ id: 3, level: 1, name: 'S' },{ id: 4, level: 0, name: 'A' },{ id: 5, level: 0, name: 'S' },{ id: 6, level: 1, name: 'A' },{ id: 7, level: 0, name: 'B' },]
var result = arr.filter(function (obj) {
return obj.level === 1 || ['B', 'S'].indexOf(obj.name) > -1;
})
console.log(result)
A simple loop would achieve this - and push the matching items into a new array that then has the desired items.
I have avoided the newer more exciting ways of doing this for the old school var and forEach approach to match your ecma-5 requirement.
var arr = [
{id: 1, level: 0, name: 'A'},
{id: 2, level: 1, name: 'B'},
{id: 3, level: 1, name: 'S'},
{id: 4, level: 0, name: 'A'},
{id: 5, level: 0, name: 'S'},
{id: 6, level: 1, name: 'A'},
{id: 7, level: 0, name: 'B'}, ]
var newArr = [];
arr.forEach(function(item) {
if(item.level === 1 || (item.name === 'A' || item.name ==='B')) {
newArr.push(item)
}
})
console.log(newArr);
//gives[
//{id: 1,level: 0, name": "A"},
//{id: 2, level: 1, name: "B"},
//{id: 3, level: 1, name: "S"},
//{id: 4, level: 0, name: "A"},
//{id: 6, level: 1, name: "A"},
//{id: 7, level: 0, name: "B"}]
const arr = [
{ id: 1, level: 0, name: 'A' },
{ id: 2, level: 1, name: 'B' },
{ id: 3, level: 1, name: 'S' },
{ id: 4, level: 0, name: 'A' },
{ id: 5, level: 0, name: 'S' },
{ id: 6, level: 1, name: 'A' },
{ id: 7, level: 0, name: 'B' },
];
const newArr = arr.filter(
item => item.level === 0 || item.name === 'B' || item.name === 'S'
);
console.log(newArr);
You can use the Array.prototype.filter() method along with the Array.prototype.indexOf() method to filter the array based on your criteria:
const array = [
{id: 1, level: 0, name: 'A'},
{id: 2, level: 1, name: 'B'},
{id: 3, level: 1, name: 'S'},
{id: 4, level: 0, name: 'A'},
{id: 5, level: 0, name: 'S'},
{id: 6, level: 1, name: 'A'},
{id: 7, level: 0, name: 'B'},
];
var names = ['B', 'S'];
var result = array.filter(item => item.level === 0 || names.indexOf(item.name) !== -1);
console.log(result)
i want to filter an array by some specific names
genres = [
{id: 1, name: 'Action'},
{id: 2, name: 'Comedy'},
{id: 3, name: 'Documentary'},
{id: 4, name: 'Action'},
{id: 5, name: 'Comedy'},
]
// in here i get only Action
genres.filter(i => i.name.includes("Action"))
output = [
{id: 1, name: 'Action'},
{id: 4, name: 'Action'},
]
// in here i get only Comedy
genres.filter(i => i.name.includes("Comedy"))
output = [
{id: 2, name: 'Comedy'},
{id: 5, name: 'Comedy'},
]
But i want to get both Action and Comedy like this:
[
{id: 1, name: 'Action'},
{id: 4, name: 'Action'},
{id: 2, name: 'Comedy'},
{id: 5, name: 'Comedy'},
]
How can i do it?
genres.filter(i => i.name.includes("Action") | i.name.includes("Comedy"))
or
genres.filter(i => ["Action","Comedy"].includes(i.name))
Use an OR operator to check on both conditions
genres = [
{id: 1, name: 'Action'},
{id: 2, name: 'Comedy'},
{id: 3, name: 'Documentary'},
{id: 4, name: 'Action'},
{id: 5, name: 'Comedy'},
]
// Use a OR operator to check both conditions
result = genres.filter(i => i.name.includes("Action") || i.name.includes("Comedy"))
console.log(result)
You can do:
const genres = [{id: 1, name: 'Action'},{id: 2, name: 'Comedy'},{id: 3, name: 'Documentary'},{id: 4, name: 'Action'},{id: 5, name: 'Comedy'},]
const result = genres.filter(({ name }) => ['Action', 'Comedy'].includes(name))
console.log(result)
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)
JavaScript ninjas! Now i have this collection:
var cats = [
{ id: 1, parent_id: 0, title: 'Movies' },
{ id: 2, parent_id: 0, title: 'Music' },
{ id: 3, parent_id: 1, title: 'Russian movies' },
{ id: 4, parent_id: 2, title: 'Russian music' },
{ id: 5, parent_id: 3, title: 'New' },
{ id: 6, parent_id: 3, title: 'Top10' },
{ id: 7, parent_id: 4, title: 'New' },
{ id: 8, parent_id: 4, title: 'Top10' },
{ id: 9, parent_id: 0, title: 'Soft' }
];
And i need this result:
var catsExtended = [
{ id: 1, parent_id: 0, childs: [ 3, 5, 6 ], title: 'Movies' },
{ id: 2, parent_id: 0, childs: [ 4, 7, 8 ], title: 'Music' },
{ id: 3, parent_id: 1, childs: [ 5, 6 ], title: 'Russian movies' },
{ id: 4, parent_id: 2, childs: [ 7, 8 ], title: 'Russian music' },
{ id: 5, parent_id: 3, childs: [], title: 'New' },
{ id: 6, parent_id: 3, childs: [], title: 'Top10' },
{ id: 7, parent_id: 4, childs: [], title: 'New' },
{ id: 8, parent_id: 4, childs: [], title: 'Top10' },
{ id: 9, parent_id: 0, childs: [], title: 'Soft' }
];
Help me pleace to collect all IDs
You could use a hash table for the reference to the already returned objects. And for the parents just iterate until parent_id becomes zero.
var cats = [{ id: 1, parent_id: 0, title: 'Movies' }, { id: 2, parent_id: 0, title: 'Music' }, { id: 3, parent_id: 1, title: 'Russian movies' }, { id: 4, parent_id: 2, title: 'Russian music' }, { id: 5, parent_id: 3, title: 'New' }, { id: 6, parent_id: 3, title: 'Top10' }, { id: 7, parent_id: 4, title: 'New' }, { id: 8, parent_id: 4, title: 'Top10' }, { id: 9, parent_id: 0, title: 'Soft' }],
catsExtended = cats.map(function (a) {
var id = a.parent_id;
this[a.id] = { id: a.id, parent_id: a.parent_id, children: [], title: a.title };
while (id) {
this[id].children.push(a.id);
id = this[id].parent_id;
}
return this[a.id];
}, Object.create(null));
console.log(catsExtended);
Combine map() and filter():
var catsExtended = cats.map(function(cat) {
return {
id: cat.id,
parent_id: cat.parent_id,
title: cat.title,
childs: cats.filter(function(c) {
return c.parent_id == cat.id;
}).map(function(c) {
return c.id
})
};
});
I think a simple Array.prototype.forEach can do a lot.
var cats = [{ id: 1, parent_id: 0, title: 'Movies' }, { id: 2, parent_id: 0, title: 'Music' }, { id: 3, parent_id: 1, title: 'Russian movies' }, { id: 4, parent_id: 2, title: 'Russian music' }, { id: 5, parent_id: 3, title: 'New' }, { id: 6, parent_id: 3, title: 'Top10' }, { id: 7, parent_id: 4, title: 'New' }, { id: 8, parent_id: 4, title: 'Top10' }, { id: 9, parent_id: 0, title: 'Soft' }];
cats.forEach(function(c) {
var pid = c.parent_id;
c.children = (this[c.id] = this[c.id] || []);
(this[pid] = (this[pid] || [])).push(c.id)
}, Object.create(null));
console.clear();
console.log(cats);