'flatten nested object with topairs in lodash

I have an object like this:

let order={
  "john":{
    "hamburger":4
  },
  "alice":{
    "pizza":10,
    "milkshake":2
  },
  "bob":{
  }
}

that I would like to flatten like this:

let res=[
   ["john","hamburger",4],

   ["alice","pizza",4],
   ["alice","milkshake",4]

]

I know how to do it in reverse, using keyBy but how can I turn it from a recursive object to a recursive array? I tried using toPairs, but I can't figure out how to make it recursive



Solution 1:[1]

Use _.toPairs() to get an array of [name, items] pairs, which you iterate with _.flatMap(). Get the pairs from the items object, and use _.map() to create the arrays that include the info:

const { flatMap, toPairs, map } = _

const order = {"john":{"hamburger":4},"alice":{"pizza":10,"milkshake":2},"bob":{}}

const result = flatMap(
  toPairs(order),
  ([name, items]) => 
    map(
      toPairs(items),
      ([product, price]) => [name, product, price]
    )
)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

In vanilla JS you can use Array.flatMap() with Object.entries() to get the same result:

const order = {"john":{"hamburger":4},"alice":{"pizza":10,"milkshake":2},"bob":{}}

const result = Object
  .entries(order)
  .flatMap(([name, items]) =>
    Object.entries(items)
      .map(([product, price]) => [name, product, price])
  )

console.log(result)

Solution 2:[2]

This solution will work recursively and let you supply a callback that determines how to construct the accumulator. Note that it only works with objects, not a mix of nested objects and arrays.

const flatten = (tree, acc, branch = [], build) =>
  Object.entries(tree).reduce(
    (a, [key, val]) =>
      typeof val !== 'object'
        ? build(a, branch, key, val)
        : flatten(val, a, branch.concat(key), build),
    acc
  );

In your case you want the branch to be an array of nodes with the final leaf also being in the same array.

const source = {
  a: {
    b: {
      c: 'c'
    }
  },
  b: {
    d: {
      x: 'sdfdsgsf'
    },
    c: {
      i: 'sdf',
      f: {
        one: {
          two: {
            three: 'deep'
          }
        }
      }
    }
  }
};

const flatten = (tree, acc, branch = [], build) =>
  Object.entries(tree).reduce(
    (a, [key, val]) =>
      typeof val !== 'object'
        ? build(a, branch, key, val)
        : flatten(val, a, branch.concat(key), build),
    acc
  );
  
 console.log(
  flatten(source, [], [], (acc, branch, key, val) => [
    ...acc,
    branch.concat(key, val)
  ])
);

output

[
  [ 'a', 'b', 'c', 'c' ],
  [ 'b', 'd', 'x', 'sdfdsgsf' ],
  [ 'b', 'c', 'i', 'sdf' ],
  [
    'b',    'c',
    'f',    'one',
    'two',  'three',
    'deep'
  ]
]

I need to concat the branch nodes into a string such as

{
  'a.b.c.d': 'string',
  'x.y.z': 'foo'
}

So I used

flatten(source, {}, [], (acc, branch, key, val) => ({
  ...acc,
  [branch.concat(key).join('.')]: val
}))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ori Drori
Solution 2 Daniel Lizik