4

I have an array with nested objects having parent-child relationship like so:

[
{id: 1, title: 'hello', parent: 0, children: [
    {id: 3, title: 'hello', parent: 1, children: [
        {id: 4, title: 'hello', parent: 3, children: [
            {id: 5, title: 'hello', parent: 4, children: []},
            {id: 6, title: 'hello', parent: 4, children: []}
        ]},
        {id: 7, title: 'hello', parent: 3, children: []}
    ]}
]},
{id: 2, title: 'hello', parent: 0, children: [
    {id: 8, title: 'hello', parent: 2, children: []}
]}
]

I need to convert it into a plain array retaining the parent child relationship like so and in the order of parent and all its children returned first before proceeding on to the next parent.

[
{id: 1, title: 'hello', parent: 0},
{id: 3, title: 'hello', parent: 1},
{id: 4, title: 'hello', parent: 3},
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4},
{id: 7, title: 'hello', parent: 3},
{id: 2, title: 'hello', parent: 0},
{id: 8, title: 'hello', parent: 2}
]

I was able to convert the other way round with a recursive function.

But I need to do the opposite in an efficient way. There is multilevel nesting as shown in the sample nested array.

EDIT: Updated the nested array to have an empty children array for leaf nodes.

And also, an answer in ES5 would help.

Priyanker Rao
  • 83
  • 1
  • 6

6 Answers6

3

I just use a simple recursive function to make an array object into a plain array

var arr = [ {id: 1, title: 'hello', parent: 0, children: [ {id: 3, title: 'hello', parent: 1, children: [ {id: 4, title: 'hello', parent: 3, children: [ {id: 5, title: 'hello', parent: 4, children: []}, {id: 6, title: 'hello', parent: 4, children: []} ]}, {id: 7, title: 'hello', parent: 3, children: []} ]} ]}, {id: 2, title: 'hello', parent: 0, children: [ {id: 8, title: 'hello', parent: 2, children: []} ]} ];

var result = [];
var convertArrToObj = (arr) => {
  arr.forEach(e => {
    if (e.children) {
      result.push({
        id: e.id,
        title: e.title,
        parent: e.parent
      });
      convertArrToObj(e.children);
    } else result.push(e);

  });
};
convertArrToObj(arr);
console.log(result);
mplungjan
  • 169,008
  • 28
  • 173
  • 236
Cadmus
  • 144
  • 1
  • 11
  • Nice and simple. – mplungjan Mar 16 '21 at 09:14
  • Thanks. I updated this solution a bit by deep cloning 'e' object and deleting children property from it. This is just in case I have a lot of properties, it'd be better to just remove the one children property. `var i= JSON.parse(JSON.stringify(e)); delete i.subTrendingTopics; result.push(i);` – Priyanker Rao Mar 16 '21 at 10:13
3

In ES5 you can also use some functional programming approach, and flatten an array with [].concat.apply:

function flatten(arr) {
    return [].concat.apply([], arr.map(function (obj) {
        return [].concat.apply([
            { id: obj.id, title: obj.title, parent: obj.parent }
        ], flatten(obj.children));
    }));
}

let arr = [{id: 1, title: 'hello', parent: 0, children: [{id: 3, title: 'hello', parent: 1, children: [{id: 4, title: 'hello', parent: 3, children: [{id: 5, title: 'hello', parent: 4, children: []},{id: 6, title: 'hello', parent: 4, children: []}]},{id: 7, title: 'hello', parent: 3, children: []}]}]},{id: 2, title: 'hello', parent: 0, children: [{id: 8, title: 'hello', parent: 2, children: []}]}];

console.log(flatten(arr));

In ES6 the same algorithm reduces to the following:

const flatten = arr => arr.flatMap(({children, ...o}) => [o, ...flatten(children)]);

let arr = [{id: 1, title: 'hello', parent: 0, children: [{id: 3, title: 'hello', parent: 1, children: [{id: 4, title: 'hello', parent: 3, children: [{id: 5, title: 'hello', parent: 4, children: []},{id: 6, title: 'hello', parent: 4, children: []}]},{id: 7, title: 'hello', parent: 3, children: []}]}]},{id: 2, title: 'hello', parent: 0, children: [{id: 8, title: 'hello', parent: 2, children: []}]}];

console.log(flatten(arr));
trincot
  • 317,000
  • 35
  • 244
  • 286
2

Using ES5 would require a lot more lines of code and like you said is not very efficient.

Here's my ES5 version, you should be able to notice the difference in performance

const data = [{id:1,title:'hello',parent:0,children:[{id:3,title:'hello',parent:1,children:[{id:4,title:'hello',parent:3,children:[{id:5,title:'hello',parent:4,children:[]},{id:6,title:'hello',parent:4,children:[]}]},{id:7,title:'hello',parent:3,children:[]}]}]},{id:2,title:'hello',parent:0,children:[{id:8,title:'hello',parent:2,children:[]}]}];

// Recursively
function reduceArrayDimension(array) {
  var level = [];

  array.forEach(function(item) {
    level.push({
      id: item.id,
      title: item.title,
      parent: item.parent
    });
    item.children.forEach(function(child) {
        reduceArrayDimension([child]).forEach(function(childItem) {
          level.push(childItem);
        });
    });
  });

  return level;
}

console.log(reduceArrayDimension(data));

And ES6

const data=[{id:1,title:'hello',parent:0,children:[{id:3,title:'hello',parent:1,children:[{id:4,title:'hello',parent:3,children:[{id:5,title:'hello',parent:4,children:[]},{id:6,title:'hello',parent:4,children:[]}]},{id:7,title:'hello',parent:3,children:[]}]}]},{id:2,title:'hello',parent:0,children:[{id:8,title:'hello',parent:2,children:[]}]}];

// Recursively
function reduceArrayDimension(array) {
  const level = [];
  
  array.forEach(item => {
    level.push({id: item.id, title: item.title, parent: item.parent});
    if (item.children) level.push(...reduceArrayDimension(item.children));
  });
  
  return level;
}

console.log(reduceArrayDimension(data));
a.mola
  • 3,883
  • 7
  • 23
  • This generates an order from child to parent. I needed parent to child. I think your previous solution gave the right output afaik. Anyway, thanks for the effort. – Priyanker Rao Mar 16 '21 at 10:33
  • @PriyankerRao I’ve edited it now for Parent to Child – a.mola Mar 16 '21 at 10:37
0

If the data is not very large, this could be a pragmatic method

const data = [{ id: 1, title: 'hello', parent: 0, children: [{ id: 3, title: 'hello', parent: 1, children: [{ id: 4, title: 'hello', parent: 3, children: [{ id: 5, title: 'hello', parent: 4 }, { id: 6, title: 'hello', parent: 4 , children:[]} ] }, { id: 7, title: 'hello', parent: 3 } ] }] }, { id: 2, title: 'hello', parent: 0, children: [{ id: 8, title: 'hello', parent: 2 }] } ];

const str = JSON.stringify(data)
  .replaceAll('"children":[',"},")
  .replaceAll("]}","")
  .replaceAll(",,",",")   // handle empty children
  .replaceAll(",}","}");


console.log(JSON.parse(str).sort((a,b) => a.id-b.id))
mplungjan
  • 169,008
  • 28
  • 173
  • 236
0

if data is large can consider use tail optimization and async/await

const arr = [
{id: 1, title: 'hello', parent: 0, children: [
    {id: 3, title: 'hello', parent: 1, children: [
        {id: 4, title: 'hello', parent: 3, children: [
            {id: 5, title: 'hello', parent: 4},
            {id: 6, title: 'hello', parent: 4}
        ]},
        {id: 7, title: 'hello', parent: 3}
    ]}
]},
{id: 2, title: 'hello', parent: 0, children: [
    {id: 8, title: 'hello', parent: 2}
]}
];

const convertArr = (arr) => {
  return arr.reduce((init, cur) => {
    const plain = init.concat(cur);
    const children = cur.children;
    return plain.concat(children && children.length ? convertArr(children) : [])
  }, [])
}

const generateArr = (arr) => {
  return convertArr(arr).map(v => ({
    id: v.id,
    parent: v.parent,
    title: v.title
  }))
}
console.log('result:', generateArr(arr))
mplungjan
  • 169,008
  • 28
  • 173
  • 236
lam
  • 545
  • 1
  • 3
  • 10
0

You can use a generator function:

var arr = [ {id: 1, title: 'hello', parent: 0, children: [ {id: 3, title: 'hello', parent: 1, children: [ {id: 4, title: 'hello', parent: 3, children: [ {id: 5, title: 'hello', parent: 4, children: []}, {id: 6, title: 'hello', parent: 4, children: []} ]}, {id: 7, title: 'hello', parent: 3, children: []} ]} ]}, {id: 2, title: 'hello', parent: 0, children: [ {id: 8, title: 'hello', parent: 2, children: []} ]} ];
function* flatten(d){
   for (var i of d){
      yield {id:i.id, title:i.title, parent:i.parent}
      yield* flatten(i.children)
   }
}
console.log([...flatten(arr)])
Ajax1234
  • 69,937
  • 8
  • 61
  • 102