Search code examples
javascriptjavascript-objectsnested-loops

Merging nested objects by conditions more than 2 levels deep


I have an array of quite simple objects:

[
  { category: 'A', level: 'Aa', sublevel: 'Aaa' },
  { category: 'A', level: 'Aa', sublevel: 'Aab' },
  { category: 'A', level: 'Ab', sublevel: 'Abb' },
  { category: 'B', level: 'Ac', sublevel: 'Abc' }
]

Sublevels are always unique, levels and categories can be same.
I want to create the following object from that array:

[
  { 
    category: 'A',
    children: [
      {
        level: 'Aa',
        children: [
          {
            sublevel: 'Aaa'
          },
          {
            sublevel: 'Aab'
          }
        ]
      },
      {
        level: 'Ab',
        children: [
          {
            sublevel: 'Abb'
          }
        ]
      }
    ]
  },
  { 
    category: 'B',
    children: [
      {
        level: 'Ac',
        children: [
          {
            sublevel: 'Abc'
}] }] }]

In other words I want to merge category into one if there are two objects with same category and place its levels into array children. If there are also objects with same categories and levels, then merge same levels into one and place sublevels into relevant array children.

Iterating through the array didn't really work out with [].map and [].reduce and various kinds of loops. Also tried lodash mergeWith and deepmerge, but sublevels seem to be invisible for them.

What are the elegant ways of constructing objects on conditions with more than 2 levels deep?


Solution

  • You just need a reducer with a nested filter/map on the original array.

    const categories = base.reduce((accumulator, data) => {
      let category;
    
      if (!accumulator.find(d => d.category === data.category)) {
        category = {
          category: data.category,
          children: [],
        };
    
        base
          .filter(d => d.category === data.category)
          .map(d => {
            if (!category.children.find(c => c.level === d.level)) {
              category.children.push({ level: d.level, children: [] });
            }
    
            const level = category.children.find(c => c.level === d.level);
    
            if (!level.children.find(c => c.sublevel === d.sublevel)) {
              level.children.push({ sublevel: d.sublevel });
            }
          })
    
    
        accumulator.push(category);
      }
    
      return accumulator;
    }, []);