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:
- get every object in
obj1
- for each of them use another loop over the keys (
a
,b
, ...) - if the key doesn't exist in
output
, create an empty array there - 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
Object.values
takes the values of all properties of an object and makes a list out of them. The rules of iteration are same asfor...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}]
Object.entries
is similar toObject.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]]
Array.flatMap
applies the passed function on all elements of an array (just likemap
), 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]]
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 becauseundefined
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] }
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 usereduce
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 (likeObject.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] }