Home > database >  Merge nested objects in javascript, and append matching properties in array
Merge nested objects in javascript, and append matching properties in array

Time:08-20

I have an object like this, with nested objects:

obj1 = { 
  1: { a:5,b:6,c:7,d:8 },
  2: { a:6,b:9,c:10,d:12,e:1 },
  3: { a:3,b:4,c:9 },
}

I want to merge all its nested objects, while grouping in a list the values of properties with the same key.
Desired output:

{ 
  a: [5,6,3],  
  b: [6,9,4],  
  c: [7,10,9],  
  d: [8,12],  
  e: [1]
}

CodePudding user response:

you can use nested loops:

  1. get every object in obj1
  2. for each of them use another loop over the keys (a, b, ...)
  3. if the key doesn't exist in output, create an empty array there
  4. add the current value to the array at the matching key in output

let obj1 = {
  1: { a: 5, b: 6, c: 7, d: 8 },
  2: { a: 6, b: 9, c: 10, d: 12, e: 1 },
  3: { a: 3, b: 4, c: 9 },
};
let output = {};

for (let n in obj1) {                  // n is a key in obj1 (1, 2, ...)
  for (key in obj1[n]) {               // key is a key in obj1[n] (a, b, ...)
    output[key] ??= [];                // create an empty array if it doesnt exist already
    output[key].push(obj1[n][key]);    // push the value to the array
  }
}

console.log(output);

CodePudding user response:

This is a good opportunity to show off the neat javascript spread syntax, Array and Object prototype functions, and destructuring patterns.

Object.values(obj1).flatMap(Object.entries).reduce(
  (acc, [k, v]) => ({ ...acc, [k]: [...acc[k]||[], v] }), {})

As simple as this!


Extended answer

  1. Object.values takes the values of all properties of an object and makes a list out of them. The rules of iteration are same as for...in
    For example:

    Object.values(obj1)
    // The result will be:
    [{a:5,b:6,c:7,d:8}, {a:6,b:9,c:10,d:12,e:1}, {a:3,b:4,c:9}]
    
  2. Object.entries is similar to Object.values above, but it generates a list of [key, val] pairs instead, one for each property.
    For example:

    Object.entries({a:5,b:6,c:7,d:8})
    // The result will be:
    [['a',5], ['b',6], ['c',7], ['d',8]]
    
  3. Array.flatMap applies the passed function on all elements of an array (just like map), but in the end, it flattens the result. That means, if the result is a list of lists, it returns only one big array with all inner elements.
    For example:

    Object.values(obj1).flatMap(Object.entries)
    // By passing Object.entries as function,
    // it will extract all the key-value pairs in one big list
    [['a',5],['b',6],['c',7],['d',8], ['a',6],['b',9],['c',10],['d',12],['e',1], ['a',3],['b',4],['c',9]]
    
  4. The spread syntax (...) is a neat feature of javascript, that is used to semantically refer to the enumeration of something.

    • When used in array literals, you can merge and append values together to form a bigger array. Like this:

       o = {a: [5, 6]}
      
       o.a = [...o.a, 3]     // all elements from o.a, appending 3
       { a:[5, 6, 3] }
      
       o.d = [...o.d, 8]     // TypeError: o.d is undefined
       o.d = [...o.d||[], 8] // if o.d is undefined, use [], then append 8
       { a:[5, 6, 3], d:[8] }
      
       // This is useful as a semantic: "append to list or create a new one"
      

      [Note 1]: [...o.d||[] works because undefined is falsy and the logical OR operator || short circuits.

    • When the spread syntax is used in object literals, you can merge objects together or append new properties. Like this:

      o = { 
        ...o,                       // all properties of o
        ...{b:[6,9,4], c:[7,10,9]}, // all properties of the literal
        d: [...o.d||[], 12],        // overriding property d
        ['e']: [1]                  // new property with computed name
      }
      // Result will be:      
      { a:[5,6,3], b:[6,9,4], c:[7,10,9], d:[8,12], e:[1] }
      
  5. Array.reduce iterates over a list, applying the passed function and accumulating the results. In the end, it returns the accumulated value. The returned value depends on the operation performed by the passed function.
    For example, you can use reduce to sum over all elements of an array.

    [1, 2, 3, 4, 5].reduce((acc, curr) => curr   acc, 0)
    // It is the same as (((((0   1)   2)   3)   4)   5)
    15
    

    However, you can also use reduce to take [key, val] pairs from a list and make them properties into an object (like Object.fromEntries, but more flexible).
    This snippet below uses a destructuring pattern to unpack the [k, v] pairs passed as function parameter, and uses [k] as computed property name. See:

    [['a',5], ['b',6], ['c',7], ['d',8]].reduce(
      (acc, [k, v]) => ({ ...acc, [k]:v }), {})
    // The result will be:
    { a:5, b:6, c:7, d:8 }
    
    [['a',5], ['b',6], ['a',6]].reduce(
      (acc, [k, v]) => ({ ...acc, [k]:v }), {})
    // Property a is repeated, so it will honor only the last value
    { a:6, b:6 }
    
    // But if we use the semantic "append or create" mentioned above,
    [['a',5], ['b',6], ['a',6]].reduce(
      (acc, [k, v]) => ({ ...acc, [k]: [...acc[k]||[], v] }), {})
    // The result will be:
    { a:[5,6], b:[6] }
    

Now just bring it all together:

Object.values(obj1).flatMap(Object.entries).reduce(
  (acc, [k, v]) => ({ ...acc, [k]: [...acc[k]||[], v] }), {})

// You can read it as:
// From all entries of the inner-lists of obj1, 
// create one big flat array of [k, v] out of them
// and go adding each one as a property into a new object acc
// grouping the value in lists.

// Final result will be:
{ a:[5,6,3], b:[6,9,4], c:[7,10,9], d:[8,12], e:[1] }    
  • Related