Merge JavaScript objects in array with same key

What is the best way to merge array contents from JavaScript objects sharing a key in common?

How can array in the example below be reorganized into output? Here, all value keys (whether an array or not) are merged into all objects sharing the same name key.

var array = [
    {
        name: "foo1",
        value: "val1"
    }, {
        name: "foo1",
        value: [
            "val2",
            "val3"
        ]
    }, {
        name: "foo2",
        value: "val4"
    }
];

var output = [
    {
        name: "foo1",
        value: [
            "val1",
            "val2",
            "val3"
        ]
    }, {
        name: "foo2",
        value: [
            "val4"
        ]
    }
];

Here is one option:-

var array = [{
  name: "foo1",
  value: "val1"
}, {
  name: "foo1",
  value: ["val2", "val3"]
}, {
  name: "foo2",
  value: "val4"
}];

var output = [];

array.forEach(function(item) {
  var existing = output.filter(function(v, i) {
    return v.name == item.name;
  });
  if (existing.length) {
    var existingIndex = output.indexOf(existing[0]);
    output[existingIndex].value = output[existingIndex].value.concat(item.value);
  } else {
    if (typeof item.value == 'string')
      item.value = [item.value];
    output.push(item);
  }
});

console.dir(output);

Here is another way of achieving that goal:

var array = [{
  name: "foo1",
  value: "val1"
}, {
  name: "foo1",
  value: [
    "val2",
    "val3"
  ]
}, {
  name: "foo2",
  value: "val4"
}];

var output = array.reduce(function(o, cur) {

  // Get the index of the key-value pair.
  var occurs = o.reduce(function(n, item, i) {
    return (item.name === cur.name) ? i : n;
  }, -1);

  // If the name is found,
  if (occurs >= 0) {

    // append the current value to its list of values.
    o[occurs].value = o[occurs].value.concat(cur.value);

    // Otherwise,
  } else {

    // add the current item to o (but make sure the value is an array).
    var obj = {
      name: cur.name,
      value: [cur.value]
    };
    o = o.concat([obj]);
  }

  return o;
}, []);

console.log(output);

Using lodash

var array = [{name:"foo1",value:"val1"},{name:"foo1",value:["val2","val3"]},{name:"foo2",value:"val4"}];

function mergeNames (arr) {
    return _.chain(arr).groupBy('name').mapValues(function (v) {
        return _.chain(v).pluck('value').flattenDeep();
    }).value();
}

console.log(mergeNames(array));

Here is a version using an ES6 Map:

const arrays = [{ name: "foo1",value: "val1" }, {name: "foo1", value: ["val2", "val3"] }, {name: "foo2",value: "val4"}];

const map = new Map(arrays.map(({name, value}) => [name, { name, value: [] }])); 
for (let {name, value} of arrays) map.get(name).value.push(...[value].flat());
console.log([...map.values()]);

2021 version

  • Using reduce to aggregate data.
  • Using logical nullish assignment only assigns if acc[name] is nullish (null or undefined).
  • Using Array.isArray to determines whether the passed value is an Array.

var arrays = [{ name: "foo1",value: "val1" }, {name: "foo1", value: ["val2", "val3"] }, {name: "foo2",value: "val4"}];

const result = arrays.reduce((acc, {name, value}) => {
  acc[name] ??= {name: name, value: []};
  if(Array.isArray(value)) // if it's array type then concat 
    acc[name].value = acc[name].value.concat(value);
  else
    acc[name].value.push(value);
  
  return acc;
}, {});

console.log(Object.values(result));