Given a flat level array of objects, what's the most efficient and modern way to nest them based on a parent and id property? The top level objects have no parentId, and there's no limit to nest levels.
[{
id: 'OS:MacOS',
type: 'OS',
value: 'MacOS'
}, {
parentId: 'OS:MacOS',
id: 'Version:Catalina',
type: 'Version',
value: 'Catalina'
}, {
parentId: 'Version:Catalina',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}, {
id: 'OS:Windows',
type: 'OS',
value: 'Windows'
}, {
parentId: 'OS:Windows',
id: 'Version:7',
type: 'Version',
value: '7'
}, {
parentId: 'OS:MacOS',
id: 'Version:Mojave',
type: 'Version',
value: 'Mojave'
}, {
parentId: 'Version:Mojave',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}, {
parentId: 'OS:Windows',
id: 'Version:XP',
type: 'Version',
value: 'XP'
}, {
parentId: 'Version:XP',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}]
Where parentId matches up to a corresponding id field. Ideally transforming them to include a children array field along the lines of:
[{
id: 'OS:MacOS',
type: 'OS',
value: 'MacOS',
children: [
{
parentId: 'OS:MacOS',
id: 'Version:Catalina',
type: 'Version',
value: 'Catalina',
children: [
{
parentId: 'Version:Catalina',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}
]
},
{
parentId: 'OS:MacOS',
id: 'Version:Mojave',
type: 'Version',
value: 'Mojave',
children: [
{
parentId: 'Version:Mojave',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}
]
}
]
}, {
id: 'OS:Windows',
type: 'OS',
value: 'Windows',
children: [
{
parentId: 'OS:Windows',
id: 'Version:7',
type: 'Version',
value: '7'
},
{
parentId: 'OS:Windows',
id: 'Version:XP',
type: 'Version',
value: 'XP',
children: [
{
parentId: 'Version:XP',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}
]
}
]
}]
Thoughts appreciated!
You could use reduce in recursive function that will pass down the current element id and compare it with parent id in nested calls.
const data = [{"id":"OS:MacOS","type":"OS","value":"MacOS"},{"parentId":"OS:MacOS","id":"Version:Catalina","type":"Version","value":"Catalina"},{"parentId":"Version:Catalina","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"id":"OS:Windows","type":"OS","value":"Windows"},{"parentId":"OS:Windows","id":"Version:7","type":"Version","value":"7"},{"parentId":"OS:MacOS","id":"Version:Mojave","type":"Version","value":"Mojave"},{"parentId":"Version:Mojave","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"parentId":"OS:Windows","id":"Version:XP","type":"Version","value":"XP"},{"parentId":"Version:XP","id":"Browser:Chrome","type":"Browser","value":"Chrome"}]
function nested(data, pid = undefined) {
return data.reduce((r, e) => {
if (e.parentId == pid) {
const obj = { ...e }
const children = nested(data, e.id);
if (children.length) obj.children = children;
r.push(obj)
}
return r;
}, [])
}
const result = nested(data);
console.log(result)
The reducer approach by Nenad works, but is pretty inefficient as it iterates through the data list n^2 times. Here is an O(n) solution:
function buildTree(data) {
const store = new Map(); // stores data indexed by it's id
const rels = new Map(); // stores array of children associated with id
const roots = []; // stores root nodes
data.forEach(d => {
store.set(d.id, d);
!rels.get(d.id) ? rels.set(d.id, []) : undefined; // noOp.;
if (!d.parentId) {
roots.push(d.id)
return;
}
const parent = rels.get(d.parentId) || [];
parent.push(d.id);
rels.set(d.parentId, parent);
});
function build(id) {
const data = store.get(id);
const children = rels.get(id);
if (children.length === 0) {
return {...data}
}
return {...data, children: children.map(c => build(c)) };
}
return roots.map(r => build(r));
}
const data = [{"id":"OS:MacOS","type":"OS","value":"MacOS"},{"parentId":"OS:MacOS","id":"Version:Catalina","type":"Version","value":"Catalina"},{"parentId":"Version:Catalina","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"id":"OS:Windows","type":"OS","value":"Windows"},{"parentId":"OS:Windows","id":"Version:7","type":"Version","value":"7"},{"parentId":"OS:MacOS","id":"Version:Mojave","type":"Version","value":"Mojave"},{"parentId":"Version:Mojave","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"parentId":"OS:Windows","id":"Version:XP","type":"Version","value":"XP"},{"parentId":"Version:XP","id":"Browser:Chrome","type":"Browser","value":"Chrome"}]
console.log(JSON.stringify(buildTree(data), null, 2))
Edit Note:
Earlier answer was class based. Removed that for simplicity. You can further optimize the space storage by changing store to be index based.
Related
I have an array of objects as the following
const sample = [
{ id: '1' },
{ id: '1.1' },
{ id: '1.1.1' },
{ id: '1.1.2' },
{ id: '1.2' },
{ id: '1.2.1' },
{ id: '1.2.1.1' },
{ id: '2' },
{ id: '2.1' }
];
I'd like to create a new array to include the children under their parent based on id property as the following
[
{
id: '1',
children: [
{
id: '1.1',
children: [
{ id: '1.1.1' },
{ id: '1.1.2' }
]
},
{
id: '1.2',
children: [
{
id: '1.2.1',
children: [{ id: '1.2.1.1' }]
}
]
}
]
},
{
id: '2',
children: [ { id: '2.1' } ]
}
]
I'm not sure how to do it or from where to start
Use a map to keep track of parents and children, then get the entries that are the roots as your result:
const data = [
{ id: '1' },
{ id: '1.1' },
{ id: '1.1.1' },
{ id: '1.1.2' },
{ id: '1.3' },
{ id: '1.3.1' },
{ id: '1.3.1.1' },
{ id: '2' },
{ id: '2.1' }
];
const map = new Map();
data.forEach(({ id }) => {
// exclude last bit to get parent id
const parent = id.split(".").slice(0, -1).join(".");
// our entry - needs to be like this since
// we want a reference to the same object
const entry = { id, children: [] };
// won't run if this is a root
if (parent)
// add child to parent
map.get(parent).children.push(entry);
// add to map
map.set(id, entry);
});
const result = Array.from(map)
// get roots - keys that only have one part
.filter(([key]) => key.split(".").length === 1)
// map to entry values
.map(([, value]) => value);
console.log(result);
.as-console-wrapper { max-height: 100% !important }
I have the following object
[
{ name: "parent", id: 1 },
{ name: "children", id: 2 },
{ name: "children", id: 3 },
{ name: "parent", id: 4 },
{ name: "children", id: 5 }
]
As you can see, parent and children are all at the same level. What I need to do is to connect the children with the parent, applying the parent id to the children arrays with a new attribute. Something like this:
[
{ name: "parent", id: 1 },
{ name: "children", id: 2, parentId: 1 },
{ name: "children", id: 3, parentId: 1 },
{ name: "parent", id: 4 },
{ name: "children", id: 5, parentId: 4 },
{ name: "children", id: 6, parentId: 4 }
]
So far, I hadn't been able to achieve the logic for this issue. Any help on this matter will be appreciated.
Thanks in advance!
You need to loop over your array and store the last encontered parent id, to be able to apply it to each subsequent child
const data = [
{ name: "parent", id: 1 },
{ name: "children", id: 2 },
{ name: "children", id: 3 },
{ name: "parent", id: 4 },
{ name: "children", id: 5 }
]
let lastParentId = null
data.forEach(el => {
if (el.name === "parent") {
lastParentId = el.id
} else {
el.parentId = lastParentId
}
})
console.log(data)
Here is a reusable solution using map:
const data = [{ name: "parent", id: 1 }, { name: "children", id: 2 }, { name: "children", id: 3 }, { name: "parent", id: 4 }, { name: "children", id: 5 }]
function map(data) {
let lastId
return data.map(e => {
if (e.name === "parent") lastId = e.id
if (e.name === "children") e.parentId = lastId
return e
})
}
console.info(map(data))
You could store parentId from the last parent and map new object with this properity or the object without.
const
data = [{ name: "parent", id: 1 }, { name: "children", id: 2 }, { name: "children", id: 3 }, { name: "parent", id: 4 }, { name: "children", id: 5 }],
result = data.map((parentId => o => {
if (o.name === 'parent') {
parentId = o.id;
return o;
}
return { ...o, parentId };
})());
console.log(result);
You can loop through the data using Array.map and check if the name in the object is parent if so, store the id in a variable return the same object. Otherwise, add the id stored to the object as parentId
let data = [{name:"parent",id:1},{name:"children",id:2},{name:"children",id:3},{name:"parent",id:4},{name:"children",id:5}]
const processData = (data) => {
let parentId = "";
return data.map(d => {
if(d.name === "parent") {
parentId = d.id;
} else {
d.parentId = parentId
}
return d;
})
}
console.log(processData(data))
.as-console-wrapper {
max-height: 100% !important;
}
Iterate the array, store the parent id of item with name equal to parent, set the children property of parent id
const test = [
{ name: "parent", id: 1 },
{ name: "children", id: 2 },
{ name: "children", id: 3 },
{ name: "parent", id: 4 },
{ name: "children", id: 5 }
];
let parentId = null;
for(let i = 0; i < test.length; i++){
if(test[i].name === "parent") {
parentId = test[i].id;
} else {
test[i].parentid = parentId;
}
}
console.log(test);
I want to store this data to mongodb, But I don't know how to loop it, Tried for a long time and couldn't get the correct answer
There is tree data
[
{
name: 'A',
children: [
{
name: 'A1',
children: [
{
name: 'A11',
children: []
}
]
},
{
name: 'A2',
children: []
},
]
},
{
name: 'B',
children: [
{
name: 'B1',
children: []
},
{
name: 'B2',
children: [
{
name: 'B21',
children: []
}
]
},
]
},
]
There is my Schema
const TempSchema = new mongoose.Schema({
name: String,
parent: { type: ObjectId, ref: 'Temp' },
}
I hope to get this result
{ _id: 5dea0671855f5d4b44774afd, name: 'A', parent: null, },
{ _id: 5dea07383ef7973e80883efd, name: 'A1', parent: 5dea0671855f5d4b44774afd, },
{ _id: 5dea07461047036d7c958771, name: 'A11', parent: 5dea07383ef7973e80883efd, },
{ _id: 5def00c05de2b22f8e6b9bfe, name: 'A2', parent: 5dea0671855f5d4b44774afd, },
...
This problem has troubled me for a long time, hope to get everyone's help, thank you!
Create ID and flat
const ObjectId = mongoose.Types.ObjectId;
const flatten = (data, parent = null) =>
data.flatMap(e => {
e._id = ObjectId();
return ([{
_id: e._id,
parent,
name: e.name
},
...flatten(e.children, e._id)])
});
const result = flatten(tree);
await Model.insertMany(result)
I need to convert this kind of array:
const obj = [{
name: 'firstLink',
type: 'topic',
id: 'ab75ca14-dc7c-4c3f-9115-7b1b94f88ff6',
spacing: 1, // root
}, {
name: 'secondLink',
type: 'source',
id: 'd93f154c-fb1f-4967-a70d-7d120cacfb05',
spacing: 2, // child of previous object
}, {
name: 'thirdLink',
type: 'topic',
id: '31b85921-c4af-48e5-81ae-7ce45f55df81',
spacing: 1, // root
}]
Into this object:
const map = {
'ab75ca14-dc7c-4c3f-9115-7b1b94f88ff6': {
name: 'firstLink',
type: 'topic',
children: {
'd93f154c-fb1f-4967-a70d-7d120cacfb05': {
name: 'secondLink',
type: 'source',
}
},
},
'31b85921-c4af-48e5-81ae-7ce45f55df81': {
name: 'thirdLink',
type: 'topic',
}
}
There might be up to 10 nestings, may be more (defined as spacing in the array).
How can i do that? I can use only pure js and lodash library.
You could use an array as reference to the inserted nested object.
var obj = [{ name: 'firstLink', type: 'topic', id: 'ab75ca14-dc7c-4c3f-9115-7b1b94f88ff6', spacing: 1, }, { name: 'secondLink', type: 'source', id: 'd93f154c-fb1f-4967-a70d-7d120cacfb05', spacing: 2, }, { name: 'thirdLink', type: 'topic', id: '31b85921-c4af-48e5-81ae-7ce45f55df81', spacing: 1, }],
map = {};
obj.forEach(function (a) {
this[a.spacing - 1][a.id] = { name: a.name, type: a.type, children: {}};
this[a.spacing] = this[a.spacing - 1][a.id].children;
}, [map]);
console.log(map);
If you do not like empty children objects, you could use this proposal. It creates children properties only if necessary.
var obj = [{ name: 'firstLink', type: 'topic', id: 'ab75ca14-dc7c-4c3f-9115-7b1b94f88ff6', spacing: 1, }, { name: 'secondLink', type: 'source', id: 'd93f154c-fb1f-4967-a70d-7d120cacfb05', spacing: 2, }, { name: 'thirdLink', type: 'topic', id: '31b85921-c4af-48e5-81ae-7ce45f55df81', spacing: 1, }],
map = {};
obj.forEach(function (a) {
this[a.spacing - 1].children = this[a.spacing - 1].children || {};
this[a.spacing - 1].children[a.id] = { name: a.name, type: a.type};
this[a.spacing] = this[a.spacing - 1].children[a.id];
}, [map]);
map = map.children;
console.log(map);
I want to add the children array to node where id = 52126f7d (or another). How do I do it?
var children = [
{ name: 'great-granchild3',
id: '2a12a10h'
},
{ name: 'great-granchild4',
id: 'bpme7qw0'
}
]
// json tree
var objects = {
name: 'all objects',
id:"2e6ca1c3",
children: [
{
name: 'child',
id: "6c03cfbe",
children: [
{ name: 'grandchild1',
id: "2790f59c"
},
{ name: 'grandchild2',
id: "52126f7d"
},
{ name: 'grandchild3',
id: "b402f14b"
},
{
name: 'grandchild4',
id: "6c03cff0",
children: [
{ name: 'great-grandchild1',
id: "ce90ffa6"
},
{ name: 'great-grandchild2',
id: "52f95f28"
}
]
}
]
},
{
name: 'child2',
id: "7693b310",
children: [
{ name: 'grandchild5',
id: "def86ecc"
},
{ name: 'grandchild6',
id: "6224a8f8"
}
]
}
]
}
to end up with
var objects = {
name: 'all objects',
id:"2e6ca1c3",
children: [
{
name: 'child',
id: "6c03cfbe",
children: [
{ name: 'grandchild1',
id: "2790f59c"
},
{ name: 'grandchild2',
id: "52126f7d",
children = [
{ name: 'great-granchild3',
id: '2a12a10h'
},
{ name: 'great-granchild4',
id: 'bpme7qw0'
}
]
},
{ name: 'grandchild3',
id: "b402f14b"
},
{
name: 'grandchild4',
id: "6c03cff0",
children: [
{ name: 'great-grandchild1',
id: "ce90ffa6"
},
{ name: 'great-grandchild2',
id: "52f95f28"
}
]
}
]
},
{
name: 'child2',
id: "7693b310",
children: [
{ name: 'grandchild5',
id: "def86ecc"
},
{ name: 'grandchild6',
id: "6224a8f8"
}
]
}
]
}
by finding the proper node first.
function getNodeById(id, node){
var reduce = [].reduce;
function runner(result, node){
if(result || !node) return result;
return node.id === id && node || //is this the proper node?
runner(null, node.children) || //process this nodes children
reduce.call(Object(node), runner, result); //maybe this is some ArrayLike Structure
}
return runner(null, node);
}
var target = getNodeById("52126f7d", objects);
target.children = children;
How about:
objects.children[0].children[1].children = children;