javascriptarraystreejavascript-objects

Convert array of objects with parent ids to a nested tree structure


I have a mock JSON like below:

const apiData = [{
        "id": 1,
        "label": "List item 1",
        "parent_id": 0
    },
    {
        "id": 5,
        "label": "List item 1",
        "parent_id": 1
    },
    {
        "id": 6,
        "label": "List item 1",
        "parent_id": 1
    },
    {
        "id": 7,
        "label": "List item 1",
        "parent_id": 1
    },
    {
        "id": 8,
        "label": "List item 1",
        "parent_id": 1
    },
    {
        "id": 9,
        "label": "List item 1",
        "parent_id": 8
    },
    {
        "id": 10,
        "label": "List item 1",
        "parent_id": 8
    },
    {
        "id": 2,
        "label": "List item 1",
        "parent_id": 0
    }
]

and I need to convert it to below:

[{
        "id": 1,
        "label": "List item 1",
        "parent_id": 0,
        "children": [{
                "id": 5,
                "label": "List item 1",
                "parent_id": 1
            },
            {
                "id": 6,
                "label": "List item 1",
                "parent_id": 1
            },
            {
                "id": 7,
                "label": "List item 1",
                "parent_id": 1
            },
            {
                "id": 8,
                "label": "List item 1",
                "parent_id": 1,
                "children": [{
                        "id": 9,
                        "label": "List item 1",
                        "parent_id": 8
                    },
                    {
                        "id": 10,
                        "label": "List item 1",
                        "parent_id": 8
                    }
                ]
            }

        ]
    },
    {
        "id": 2,
        "label": "List item 1",
        "parent_id": 0
    }
]

The condition based on which we need to make changes is as follows: if corresponding to a particular id, if we have a parent_id then we need to add one property children in the individual object and put values as an array of matched parent_id object.

I tried writing code for this and I came very close but I am unable to move forward.

Kindly suggest.

My code:

const apiData = [
{'id': 1, 'label': 'List item 1', 'parent_id' : 0 },
{'id': 5, 'label': 'List item 1', 'parent_id' : 1 },
{'id': 6, 'label': 'List item 1', 'parent_id' : 1 },
{'id': 7, 'label': 'List item 1', 'parent_id' : 1 },
{'id': 8, 'label': 'List item 1', 'parent_id' : 1},
{'id': 9, 'label': 'List item 1', 'parent_id' : 8 },
{'id': 10, 'label': 'List item 1', 'parent_id' : 8 },
{'id': 2, 'label': 'List item 1', 'parent_id' : 0 },
];

function compare(a, b) {
  const idA = a.id;
  const idB = b.id;
  let comparison = 0;
  comparison = idA > idB ? 1 : (idA < idB ? -1 : 0);
  return comparison;
}

const sortedApiData = apiData.sort(compare);
const newSortedApiData = [...sortedApiData];
let a = []; 


for(let i = 0; i < sortedApiData.length ; i++){
  for(let j = 0 ; j < sortedApiData.length ; j++){
     if(i === j){
       continue;
     }
     else{
       if(sortedApiData[i].id === sortedApiData[j].parent_id){
         a.push(sortedApiData[j]);

       }
     }
  }

}

console.log(a);


Solution

  • this is a similar case like this one, but with jso items.
    Javascript list loop/recursion to create an object
    items are assumed to be in good working order

    const apiData =
          [ { id: 1,  label: 'List item 1',  parent_id: 0 }
          , { id: 5,  label: 'List item 1',  parent_id: 1 }
          , { id: 6,  label: 'List item 1',  parent_id: 1 }
          , { id: 7,  label: 'List item 1',  parent_id: 1 }
          , { id: 8,  label: 'List item 1',  parent_id: 1 }
          , { id: 9,  label: 'List item 1',  parent_id: 8 }
          , { id: 10, label: 'List item 1',  parent_id: 8 }
          , { id: 2,  label: 'List item 1',  parent_id: 0 }
          ];
    
    const expected = 
          [ { id: 1, label: 'List item 1', parent_id: 0, children: 
              [ { id: 5, label: 'List item 1', parent_id: 1 } 
              , { id: 6, label: 'List item 1', parent_id: 1 } 
              , { id: 7, label: 'List item 1', parent_id: 1 } 
              , { id: 8, label: 'List item 1', parent_id: 1, children: 
                  [ { id: 9,  label: 'List item 1', parent_id: 8 } 
                  , { id: 10, label: 'List item 1', parent_id: 8 } 
            ] } ] } 
          , { id: 2, label: 'List item 1', parent_id: 0 } 
          ]; 
      
    let output = []
      , pArr   = [{arr:output,id:0}]
      ;
    for (let el of apiData)
      {
      let idx = pArr.findIndex(p=>p.id===el.parent_id);
      if(!Array.isArray(pArr[idx].arr))
        { pArr[idx].arr = pArr[idx].arr.children = [] }
      pArr[idx].arr.push(nv = Object.assign({}, el) )
      pArr[++idx] = { arr: nv, id:el.id }  // possible parent
      }
    
    console.log ('output is expected ?', (JSON.stringify(output) === JSON.stringify(expected)))
    console.log( 'output', output )

    "for the record" : I made the same code but placed inside an Array.prototype.reduce :

    let result = apiData.reduce((pArr,el,ix)=>
      {
      if (Number.isInteger(pArr))   // on ix===0
        { pArr = [{arr:[],id:0,ln:--pArr}]}
    
      let idx = pArr.findIndex(p=>p.id===el.parent_id);
      if(!Array.isArray(pArr[idx].arr))
        { pArr[idx].arr = pArr[idx].arr.children = [] }
    
      pArr[idx].arr.push(nv = Object.assign({}, el) )
      pArr[++idx] = { arr: nv, id:el.id }  // possible parent
    
      return (ix<pArr[0].ln) ? pArr : pArr[0].arr
      }
      , apiData.length ); 
    
    // proof:
    console.log ('result is expected ?', (JSON.stringify(result) === JSON.stringify(expected)))