enchance
enchance

Reputation: 30421

Flatten array of objects with nested children

I've been trying to create a generic function which could flatten an array of objects but am failing at every turn. JS isn't my home language. Does anyone know of any existing function which could accept an array of nested objects and output a flattened one?

Input:

const arr = [
    {path:'/foo', component: SomeComponent, children: [
            {path:'/one', component: SomeComponent},
            {path:'/two', component: SomeComponent},
            {path:'/three', component: SomeComponent},
    ]},
    {path: '/bar', component: SomeComponent}
]

Expected output:

const flattened_arr = [
    {path:'/foo', component: SomeComponent},
    {path:'/foo/one', component: SomeComponent},
    {path:'/foo/two', component: SomeComponent},
    {path:'/foo/three', component: SomeComponent},
    {path:'/bar', component: SomeComponent},
]

Upvotes: 1

Views: 6792

Answers (4)

Aleksandar Hristov
Aleksandar Hristov

Reputation: 618

I recently had to solve this problem to create a nested dropdown and here is my solution in case some one need to have the parents history tracked to do things like enter image description here

and also be able to send back to the Back-End every ID that is necessary

I am sharing the pure flatten and flatten on steroids version to record the parents

P.P.S. the code can easily be reused to create "path" just concat the data you need instead of keeping it an array as I had to do.

const items = [
  { id: 1, title: 'one', children: [{ id: 3, title: 'one`s child', children: [] }] },
  { id: 2, title: 'two', children: [] },
  {
    id: 4,
    title: 'three',
    children: [
      {
        id: 5,
        title: 'three`s child',
        children: [
          {
            id: 6,
            title: 'three`s grandchild',
            children: [{ id: 7, title: 'three`s great-grandchild', children: [] }],
          },
        ],
      },
    ],
  },
]

/**
 * @param items - [{..., children: ...}]
 * @info children key is remove and parents is set instead
 * @returns flatten array and remember parents in array
 */
const deepFlattenRememberParents = (items) => {
  const flatten = JSON.parse(JSON.stringify(items)) // Important - create a deep copy of 'items' / preferably use lodash '_.cloneDeep(items)', but for the example this will do
  for (let i = 0; i < flatten.length; i++) {
    if (flatten[i].hasOwnProperty('children')) {
      flatten[i].children.map((child) => {
        if (flatten[i].hasOwnProperty('parents')) {
          child.parents = [...flatten[i].parents, flatten[i]]
        } else {
          child.parents = [flatten[i]]
        }
        return child
      })

      flatten.splice(i + 1, 0, ...flatten[i].children)
      delete flatten[i].children
    }

    if (!flatten[i].hasOwnProperty('parents')) {
      flatten[i].parents = []
    }
  }

  return flatten
}

/**
 * @param items - [{..., children: ...}]
 * @returns flatten array
 */
const deepFlatten = (items) => {
  const flatten = JSON.parse(JSON.stringify(items)) // Important - create a deep copy of 'items' / preferably use lodash '_.cloneDeep(items)', but for the example this will do
  for (let i = 0; i < flatten.length; i++) {
    if (flatten[i].hasOwnProperty('children')) {
      flatten.splice(i + 1, 0, ...flatten[i].children)
      delete flatten[i].children
    }
  }

  return flatten
}

console.log('deepFlattenRememberParents ', deepFlattenRememberParents(items))
console.log('deepFlatten ', deepFlatten(items))

Upvotes: 0

Atishay Jain
Atishay Jain

Reputation: 1445

For the example above, this should do.

const result = []
arr.map((obj) => {
  if (obj.children) {
    const el = {...obj, ...{}}
    delete el.children
    result.push(el) 
    Object.values(obj.children).map((v, i) => {
      result.push(v)
    })
  } else {
    result.push(obj)
  }
})

console.log(result)

Upvotes: 2

Dominik Schreiber
Dominik Schreiber

Reputation: 2771

So there's Array.prototype.flat, but that doesn't deal with lists of Objects where one key (how should it know, which) should be flattened.

But you can always resort to Array.prototype.reduce to achieve that yourselves:

const SomeComponent = 'SomeComponent';
const arr = [
    {path:'/foo', component: SomeComponent, children: [
            {path:'/one', component: SomeComponent},
            {path:'/two', component: SomeComponent},
            {path:'/three', component: SomeComponent}
    ]},
    {path: '/bar', component: SomeComponent}
];

function myFlat(a, prefix = '') {  
  return a.reduce(function (flattened, {path, component, children}) {
    path = prefix + path;
    
    return flattened
      .concat([{path, component}])
      .concat(children ? myFlat(children, path) : []);
  }, []);
}

console.log(myFlat(arr));

Upvotes: 2

Abito Prakash
Abito Prakash

Reputation: 4770

You can try this

flattenArr = arr => {
    const result = [];
    arr.forEach(item => {
        const {path, component, children} = item;
        result.push({path, component});
        if (children)
            result.push(...flattenArr(children));
    });
    return result;
}

Upvotes: 0

Related Questions