'merge sub-array if other key-value pair match

I have this simple array, which I want to merge key b value if key a in the whole array matches.

const array = [
  {a: 1, b: ['Foo']},
  {a: 1, b: ['Bar']},
  {a: 1, b: ['Baz']},
  {a: 2, b: ['Foo']},
  {a: 3, b: ['Foo']},
]

into

const array = [
  {a: 1, b: ['Foo','Bar','Baz']},
  {a: 2, b: ['Foo']},
  {a: 3, b: ['Foo']},
]

is there a way to do it? Any help would be appreciated!

EDIT 1 : What I am lacking here is the logic to grab all similar key a



Solution 1:[1]

Use reduce to iterate over the array of objects to create an object where the keys match the object's a value, adding the various b values to their arrays. Then use Object.values to create a new array.

const arr = [
  {a: 1, b: ['Foo']},
  {a: 1, b: ['Bar']},
  {a: 1, b: ['Baz']},
  {a: 2, b: ['Foo']},
  {a: 3, b: ['Foo']},
];

// For each iteration pass in the accumulator
// and the current object
const out = arr.reduce((acc, c) => {

  // Assign the value of `a` to `key`
  const key = c.a;
  
  // If the key doesn't exist on the accumulator
  // set a new object
  acc[key] = acc[key] || { a: key, b: [] };

  // And then push the first element of `b`
  // into the object's array
  acc[key].b.push(c.b[0]);

  // Return the accumulator
  return acc;

}, {});

// And then finally get the Object.values from
// your returned object to make an array
console.log(Object.values(out));

Solution 2:[2]

You can use reduce for that

First you create an object with the value of a as key and you group all b with the same a

Then you simply remove the object keys using Object.values

const array = [
  {a: 1, b: ['Foo']},
  {a: 1, b: ['Bar']},
  {a: 1, b: ['Baz']},
  {a: 2, b: ['Foo']},
  {a: 3, b: ['Foo']},
]

const grouped =  Object.values(array.reduce(
  (res, {a, b}) => {
    const existing = res[a] || {b: []}
    return {
      ...res,
      [a]: {a, b: [...existing.b, ...b]}
    }
  }, {}
))

console.log(grouped)

Solution 3:[3]

Using reduce you can merge the values of b and create a new array.

const array = [
  {a: 1, b: ['Foo']},
  {a: 1, b: ['Bar']},
  {a: 1, b: ['Baz']},
  {a: 2, b: ['Foo']},
  {a: 3, b: ['Foo']},
];

const result = array.reduce((t, { a, b }, _, arr) => {
  if (t.some(v => v.a === a))
    return t; // if a is already handled, skip
  return [
    ...t,
    {
      a,
      b: arr
        .filter(v => v.a === a) // filter out items with different "a" value
        .reduce((t, v) => [...t, ...v.b], []), // merge the values of "b"
    }
  ];
}, []);

console.log(result);

Solution 4:[4]

Here I use Set to make sure no duplicate values. And finally map it back to an array.

const array = [
  {a: 1, b: ['Foo']},
  {a: 1, b: ['Bar']},
  {a: 1, b: ['Baz']},
  {a: 2, b: ['Foo']},
  {a: 3, b: ['Foo']},
]

const o = array.reduce((acc, obj) => {
  if(!(acc[obj?.a] instanceof Set))
    acc[obj.a] = new Set()
  acc[obj.a].add(obj.b)
  return acc
}, {})

const result = Object.entries(o).map(([a, b]) => ({a, b: Array.from(b) }))

console.log(result)

Solution 5:[5]

Using JavaScript Map

const array = [
  { a: 1, b: ["Foo"] },
  { a: 1, b: ["Bar"] },
  { a: 1, b: ["Baz"] },
  { a: 2, b: ["Foo"] },
  { a: 3, b: ["Foo"] },
];

const grouped = Array.from(
  array
    .reduce((m, o) => {
      if (m.has(o.a)) {
        const currObj = m.get(o.a);
        m.set(o.a, { ...currObj, b: [...currObj.b, ...o.b] });
      } else {
        m.set(o.a, o);
      }
      return m;
    }, new Map())
    .values()
);

console.log(grouped);

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 Andy
Solution 2 R4ncid
Solution 3
Solution 4
Solution 5 Anonymous Panda