javascriptarraysobject

Creating a hierarchical array of objects with many sub-levels


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?


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.