javascriptrecursiondata-structuresmappingreducing

How to entirely level (map / reduce / recursion) a nested object of unknown depth in the most efficient manner?


I would like to do something like the following but in a more large scale and efficient way. Assume I have an array of objects where each object needs to be leveled/flattened.

Convert something like this ...

[{
  name: 'John Doe',
  address: {
    apartment: 1550,
    streetno: 167,
    streetname: 'Victoria',
  },
}, {
  name: 'Joe Smith',
  address: {
    apartment: 2,
    streetno: 111,
    streetname: 'Jones',
  },
}]

... to that ...

[{
  name: 'John Doe',
  apartment: 1550,
  streetno: 167,
  streetname: 'Victoria',
}, {
  name: 'Joe Smith',
  apartment: 2,
  streetno: 111,
  streetname: 'Jones',
}]

As is shown above, address as well is an object which needs to be leveled/flattened.

But most importantly, one does not know the object/data-structure in advance. Thus one neither knows property-names nor the depth of the nested levels.


Solution

  • "So before receiving the object you do not know much about its structure."

    The OP's main task actually is to level any given nested object-based data-structure into an object of just a single entries-level. And because one does not know anything about a data-structure in advance, one has to come up with a recursive approach.

    Once implemented, such a function of cause can be used as callback for an array's mapping process.

    The recursive implementation itself is based on type-detection (distinguish in between Array- and Object-types and primitive values) and on reduceing the entries (key-value pairs) of an object according to the currently processed value's type.

    function recursivelyLevelObjectEntriesOnly(type) {
      let result = type;
      if (Array.isArray(type)) {
    
        result = type
          .map(recursivelyLevelObjectEntriesOnly);
    
      } else if (type && 'object' === typeof type) {
    
        result = Object
          .entries(type)
          .reduce((merger, [key, value]) => {
    
            if (value && 'object' === typeof value && !Array.isArray(value)) {
    
              Object.assign(merger, recursivelyLevelObjectEntriesOnly(value));
            } else {
              merger[key] = recursivelyLevelObjectEntriesOnly(value);
            }
            return merger;
    
          }, {});    
      }
      return result;
    }
    
    const sampleData = [{
      name: 'John Doe',
      address: { apartment: 1550, streetno: 167, streetname: 'Victoria' },
    }, {
      name: 'Joe Smith',
      address: { apartment: 2, streetno: 111, streetname: 'Jones' },
    }, {
      foo: {
        bar: "bar",
        baz: "baz",
        biz: {
          buzz: "buzz",
          bizz: [{
            name: 'John Doe',
            address: { apartment: 1550, streetno: 167, streetname: 'Victoria' },
          }, {
            name: 'Joe Smith',
            address: { apartment: 2, streetno: 111, streetname: 'Jones' },
          }, {
            foo: {
              bar: "bar",
              baz: "baz",
              biz: {
                buzz: "buzz",
                booz: {
                  foo: "foo",
                },
              },
            },
          }],
          booz: {
            foo: "foo",
          },
        },
      },
    }];
    
    const leveledObjectData = sampleData.map(recursivelyLevelObjectEntriesOnly);
    console.log({ leveledObjectData });
    
    // no mutation at `sampleData`.
    console.log({ sampleData });
    .as-console-wrapper { min-height: 100%!important; top: 0; }