简体   繁体   中英

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);

You can build the result tree by walking the array once and assigning nodes to an object that contains nodes by id, creating children arrays as necessary.

After building the tree, assuming the data is well-formed and there is at least one node whose parent id does not exist in the tree (in your data array, there is no such id: "0" node), return that node's children. Sorting is only necessary if each level should be ordered in a particular way.

It's a bit more complicated if the data is not well formed; in that case, the result should be the merged children of every node that references a nonexistent parent, but I'll omit this until motivation is present.

Lastly, since the sort and reduce operations mutate the input, we can call .map(e => ({...e})) to create a copy and keep the function pure.

 const unflatten = data => { const tree = data.map(e => ({...e})) .sort((a, b) => a.id - b.id) .reduce((a, e) => { a[e.id] = a[e.id] || e; a[e.parent_id] = a[e.parent_id] || {}; const parent = a[e.parent_id]; parent.children = parent.children || []; parent.children.push(e); return a; }, {}) ; return Object.values(tree) .find(e => e.id === undefined).children; }; const apiData = [{ "id": 1, "label": "List item 1", "parent_id": 0 }, { "id": 9, "label": "List item 1", "parent_id": 8 }, { "id": 8, "label": "List item 1", "parent_id": 1 }, { "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": 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 } ]; const unflattened = unflatten(apiData); console.log("Matches expected? " + (JSON.stringify(unflattened) === JSON.stringify(expected))); console.log(unflattened);

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)))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM