Flatten nested objects with lodash
Asked Answered
M

4

10

I'm banging my head against a well trying to do the following with lodash. I have an array of objects with more nested objects, which looks like this:

[{
    id: 123,
    name: 'John',
    summary1: {
        count: 3,
        sum: 10,
    },
    summary2: {
        count: 10,
        sum: 20
    },
},
...
]

I want to convert each element of this array into something like this:

[{
    id: 123,
    name: 'John',
    summary1_count: 3,
    summary1_sum: 10
    summary2_count: 10,
    summary2_sum: 20,
},
...
]

SO basically I want to "flatten" each element of the array, in a way such that object keys are determined based on the main and subkeys. How can I accomplish this using lodash or plain JS?

You can assume there's just 1 level of nesting like in the example.

Mountaineer answered 19/5, 2017 at 17:46 Comment(0)
D
3

You can do this without lodash (or another library) by iterating over the keys of each item in the original array, checking their type, and further iterating over their internal keys, building new keys to hold the right values.

const array = [{
  id: 123,
  name: 'John',
  summary1: {
    count: 3,
    sum: 10
  },
  summary2: {
    count: 10,
    sum: 20
  }
}];


let newArray = array.map(item => {
  let newObj = {};

  Object.keys(item).forEach(key => {
    if (typeof item[key] === 'object') {
      Object.keys(item[key]).forEach(innerKey => {
        newObj[`${key}_${innerKey}`] = item[key][innerKey];
      });
    } else {
      newObj[key] = item[key];
    }
  });

  return newObj;
});

console.log(newArray);

Granted, this isn't necessarily very pretty or flexible (following your assumption of a single level deep).

Demerit answered 19/5, 2017 at 18:6 Comment(1)
Since typeof null is still object, you may want to add this to the condition as well to avoid bugsUsa
R
13

You can simply use the npm package flat.

import flat from 'flat';

const array = [
  {
    a: 'a',
    b: {
      bb: 'bb',
    },
    c: {
      cc1: 'cc1',
      cc: {
        ccc: 'ccc',
        ccd: 'ccd',
      },
    },
  },
  // ...
];

const flattenedArray = array.map(flat);

/*
  flattenedArray === [
    {
      a: 'a',
      b.bb: 'bb',
      c.cc1: 'cc1',
      c.cc.ccc: 'ccc',
      c.cc.ccd: 'ccd',
    },
    // ...
  ]
*/

And if you want to implement it yourself, here's my implementation of flat:

const flat = (obj, concatenator = '.') => (
  Object.keys(obj).reduce(
    (acc, key) => {
      if (typeof obj[key] !== 'object' || !obj[key]) {
        return {
          ...acc,
          [key]: obj[key],
        };
      }

      const flattenedChild = flat(obj[key], concatenator);

      return {
        ...acc,
        ...Object.keys(flattenedChild).reduce((childAcc, childKey) => ({ ...childAcc, [`${key}${concatenator}${childKey}`]: flattenedChild[childKey] }), {}),
      };
    },
    {},
  )
);

const flattenedArray = array.map(o => flat(o));

Those solutions will work with any depth.

Resign answered 20/2, 2018 at 22:14 Comment(0)
D
3

You can do this without lodash (or another library) by iterating over the keys of each item in the original array, checking their type, and further iterating over their internal keys, building new keys to hold the right values.

const array = [{
  id: 123,
  name: 'John',
  summary1: {
    count: 3,
    sum: 10
  },
  summary2: {
    count: 10,
    sum: 20
  }
}];


let newArray = array.map(item => {
  let newObj = {};

  Object.keys(item).forEach(key => {
    if (typeof item[key] === 'object') {
      Object.keys(item[key]).forEach(innerKey => {
        newObj[`${key}_${innerKey}`] = item[key][innerKey];
      });
    } else {
      newObj[key] = item[key];
    }
  });

  return newObj;
});

console.log(newArray);

Granted, this isn't necessarily very pretty or flexible (following your assumption of a single level deep).

Demerit answered 19/5, 2017 at 18:6 Comment(1)
Since typeof null is still object, you may want to add this to the condition as well to avoid bugsUsa
D
1

Just use flat

import { flatten } from 'flat'

flatten({
    key1: {
        keyA: 'valueI'
    },
    key2: {
        keyB: 'valueII'
    },
    key3: { a: { b: { c: 2 } } }
})

// {
//   'key1.keyA': 'valueI',
//   'key2.keyB': 'valueII',
//   'key3.a.b.c': 2
// }
Delsiedelsman answered 9/2 at 8:22 Comment(0)
B
0

There are solution for processing nested objects as well without loadash

function objectToPathsValues(item: any): { [key: string]: any } {

    let newObj = {};
    let path = '';

    flattingNestedObject(item,path, newObj);

    return newObj;

}
function flattingNestedObject(item: any,path: string, output: { [key: string]: any }): void {
    Object.keys(item).forEach(key => {
        const combinedPath = path ? `${path}.${key}` : key;
        if (typeof item[key] === 'object') {

            flattingNestedObject(item[key],combinedPath, output);
        } else {
            output[combinedPath] = item[key];
        }
    });
}

and just call

objectToPathsValues(yourHugeObjectWithNestedObjectChildren);

The only one remark your object has not have circular nested objects.

Burroughs answered 21/3, 2023 at 15:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.