I have an array of objects (possible thousands) that may or not be nested (possible tens of nests). A subset of the data looks like this:
let data = [
{"id":"m1", "name":"M1"},
{"id":"m2", "name":"M2"},
{"parent_id":"m2", "id":"s1", "name":"S1"},
{"parent_id":"m2", "id":"s2", "name":"S2"},
{"parent_id":"m2", "id":"s3", "name":"S3"},
{"parent_id":"s3", "id":"b1", "name":"B1"}
];
This list looks pretty ordered but in real life, the objects can appear anywhere in the array ie. not ordered. Also note that the objects at the top layer do not have a parent_id. I'd like the array to be converted into:
newData = [
{ id: 'm1', name: 'M1'},
{ id: 'm2', name: 'M2', children:
[ { parent_id: 'm2', id: 's1', name: 'S1'},
{ parent_id: 'm2', id: 's2', name: 'S2'},
{ parent_id: 'm2', id: 's3', name: 'S3', children:
{ parent_id: 's3', id: 'b11', name: 'B1'}
} ]
}
]
I have looked for various ideas and figured this answer came close so here is where I started:
const createDataTree = dataset => {
let hashTable = Object.create(null)
dataset.forEach( aData => hashTable[aData.id] = { ...aData, children : [] } )
let dataTree = []
dataset.forEach( aData => {
if( aData.parent_id ) hashTable[aData.parent_id].children.push(hashTable[aData.id])
else dataTree.push(hashTable[aData.id])
} )
return dataTree
};
console.log(createDataTree(data));
Two initial issues were evident- I don't want objects without children to have an empty property- children:[] and this code only works with a single child level, not multiples.
For the first issue, I tried:
const createDataTree = dataset => {
let hashTable = Object.create(null)
let dataTree = []
dataset.forEach( aData => {
if( !aData.parent_id ) hashTable[aData.id] = { ...aData, children : [] }
if( aData.parent_id ) hashTable[aData.parent_id].children.push(hashTable[aData.id])
else dataTree.push(hashTable[aData.id])
} )
return dataTree
};
console.log(createDataTree(data))
This didn't work (all objects still had a children:[]) and also I don't understand how hashTable references dataTree such that dataTree is auto-magic-ally updated when a hashTable push occurs.
I like this approach because of its performance with large arrays- what changes are needed for children:[] and multiple levels, or is there a faster solution?
Don't create the children
property when you create a node. Add the children
property when you add the first child:
const data = [{id:"m1",name:"M1"},{id:"m2",name:"M2"},{parent_id:"m2",id:"s1",name:"S1"},{parent_id:"m2",id:"s2",name:"S2"},{parent_id:"m2",id:"s3",name:"S3"},{parent_id:"s3",id:"b1",name:"B1"}];
function createDataTree(data) {
const hashTable = data.reduce((acc, el) => (acc[el.id] = el, acc), {});
return data.reduce((acc, el) => {
if ("parent_id" in el)
if ("children" in hashTable[el.parent_id])
hashTable[el.parent_id].children.push(hashTable[el.id]);
else
hashTable[el.parent_id].children = [hashTable[el.id]];
else
acc.push(hashTable[el.id]);
return acc;
}, []);
}
console.log(createDataTree(data));
It also handles nested nodes.