80

What's the correct way to merge two arrays in Javascript?

I've got two arrays (for example):

var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]

I want to be able to end up with something like:

var a3 = [{ id : 1, name : "test", count : "1"}, 
          { id : 2, name : "test2", count : "2"}]

Where the two arrays are being joined based on the 'id' field and extra data is simply being added.

I tried to use _.union to do this, but it simply overwrites the values from the second array into the first one

Inigo
  • 12,186
  • 5
  • 41
  • 70
Tam2
  • 1,337
  • 3
  • 16
  • 23

18 Answers18

65

Short ES6 solution

const a3 = a1.map(t1 => ({...t1, ...a2.find(t2 => t2.id === t1.id)}))
Andreas Tzionis
  • 1,019
  • 9
  • 17
45

This should do the trick:

var mergedList = _.map(a1, function(item){
    return _.extend(item, _.findWhere(a2, { id: item.id }));
});

This assumes that the id of the second object in a1 should be 2 rather than "2"

Gruff Bunny
  • 27,738
  • 10
  • 72
  • 59
  • 12
    This won't work if a2 has more items in the array than a1. – CAOakley Oct 07 '16 at 20:39
  • 1
    @CAOakley - yes it will. What makes you think it doesn't work? – Gruff Bunny Oct 07 '16 at 21:00
  • @AndreaPuddu `_.unionBy` is in Lodash, not Underscore, but good to know! – 4castle Oct 25 '16 at 23:15
  • 1
    @4castle hehe lodash, of course... anyone still using underscore? :P – Andrea Puddu Oct 25 '16 at 23:22
  • Fantastic answer, Ive used this particular solution more than once. Very fast and easy to understand. – David O'Regan Aug 15 '17 at 07:02
  • 6
    [Lodash](https://github.com/lodash/lodash/wiki/Changelog#v400) has deprecated `_.findWhere` in favor of `_.find` with an iteratee so use `_.find(a2, { id: item.id })` instead. – moeabdol Mar 05 '18 at 19:04
  • @GruffBunny why do I get a reference error `ReferenceError: _ is not defined` when attempting to run this code (on node runtime)? Does this require the underscore library? I thought this was just shorthand syntax – GPP May 15 '21 at 10:28
  • Oh I see, it's using lodash. great – GPP May 15 '21 at 10:37
  • Using `find` causes the runtime to grow quadratically with the input size. Most other answers below suffer from this problem, too. It is much more efficient (linear time) to create a lookup table by `id` first, for example using `_.groupBy`. The answer by @oriol (https://stackoverflow.com/a/30335130/1166087) takes this approach. – Julian Dec 27 '21 at 11:44
28

Assuming IDs are strings and the order does not matter, you can

  1. Create a hash table.
  2. Iterate both arrays and store the data in the hash table, indexed by the ID. If there already is some data with that ID, update it with Object.assign (ES6, can be polyfilled).
  3. Get an array with the values of the hash map.
var hash = Object.create(null);
a1.concat(a2).forEach(function(obj) {
    hash[obj.id] = Object.assign(hash[obj.id] || {}, obj);
});
var a3 = Object.keys(hash).map(function(key) {
    return hash[key];
});

In ECMAScript6, if the IDs are not necessarily strings, you can use Map:

var hash = new Map();
a1.concat(a2).forEach(function(obj) {
    hash.set(obj.id, Object.assign(hash.get(obj.id) || {}, obj))
});
var a3 = Array.from(hash.values());
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • 1
    This solution has better asymptotic complexity (linear) than most other solutions (quadratic). You can write it more concisely using Underscore (or Lodash): `a3 = _.map(_.groupBy(a1.concat(a2), 'id'), arr => _.extend.apply(null, arr));`. Similar answer to a similar question here: https://stackoverflow.com/a/70474201/1166087. – Julian Dec 27 '21 at 11:38
17

ES6 simplifies this:

let merge = (obj1, obj2) => ({...obj1, ...obj2});

Note that repeated keys will be merged, and the value of the second object will prevail and the repeated value of the first object will be ignored.

Example:

let obj1 = {id: 1, uniqueObj1Key: "uniqueKeyValueObj1", repeatedKey: "obj1Val"};
let obj2 = {id: 1, uniqueObj2Key: "uniqueKeyValueObj2", repeatedKey: "obj2Val"};

merge(obj1, obj2)
// {id: 1, uniqueObj1Key: "uniqueKeyValueObj1", repeatedKey: "obj2Val", uniqueObj2Key: "uniqueKeyValueObj2"}
merge(obj2, obj1)
// {id: 1, uniqueObj2Key: "uniqueKeyValueObj2", repeatedKey: "obj1Val", uniqueObj1Key: "uniqueKeyValueObj1"}

Complete solution (with Lodash, not Underscore)

var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]
var merge = (obj1, obj2) => ({...obj1, ...obj2});
_.zipWith(a1, a2, merge)
(2) [{…}, {…}]
   0: {id: 1, name: "test", count: "1"}
   1: {id: 2, name: "test2", count: "2"}

If you have an array of arrays to merge you can do it like this:

var arrayOfArraysToMerge = [a1, a2, a3, a4]; //a3 and a4 are arrays like a1 and a2 but with different properties and same IDs.
_.zipWith(...arrayOfArraysToMerge, merge)
(2) [{…}, {…}]
   0: {id: 1, name: "test", count: "1", extra1: "val1", extra2: 1}
   1: {id: 2, name: "test2", count: "2", extra1: "val2", extra2: 2}
Alberto
  • 1,423
  • 18
  • 32
  • 4
    This is not an answer to the question. Please do check the expected result and improve your answer, or remove it. – Stephan Bijzitter Dec 01 '19 at 22:20
  • @StephanBijzitter I updated the answer to do exactly what the question was asking for, you can check it now :) Anyways I was giving him just a tool to solve his problem but not the full answer so he can learn also a bit without copy/pasting. – Alberto Dec 02 '19 at 10:49
  • If it's an answer it is an answer not a tool to discover the answer. What makes your answer better is A. not using a library to achieve the answer and B. explaining your answer so well it teaches people exactly how to answer the question the next time without copy and pasting. – Christian Matthew Dec 28 '19 at 21:46
  • 2
    I don't think this works if the lists aren't in the same order. – user3067860 Aug 20 '20 at 21:44
  • 1
    As @user3067860 points out this relies on the list being in the same order This is not to invalidate Alberto's answer just to point out. – greaterKing Jan 05 '21 at 18:24
12

reduce version.

var a3 = a1.concat(a2).reduce((acc, x) => {
    acc[x.id] = Object.assign(acc[x.id] || {}, x);
    return acc;
}, {});
_.values(a3);

I think it's common practice in functional language.

Daishi Nakajima
  • 1,932
  • 18
  • 26
  • 2
    If you wish to skip using lodash, simple way would be `Object.values` or `Object.keys(a3).map(k => a3[k])` – Rajesh May 30 '19 at 06:38
5

Already there are many great answers, I'll just add another one which is from a real problem I needed to solve yesterday.

I had an array of messages with user ids, and one array of users containing users' names and other details. This is how I managed to add user details to the messages.

var messages = [{userId: 2, content: "Salam"}, {userId: 5, content: "Hello"},{userId: 4, content: "Moi"}];
var users = [{id: 2, name: "Grace"}, {id: 4, name: "Janetta"},{id: 5, name: "Sara"}];

var messagesWithUserNames = messages.map((msg)=> {
  var haveEqualId = (user) => user.id === msg.userId
  var userWithEqualId= users.find(haveEqualId)
  return Object.assign({}, msg, userWithEqualId)
})
console.log(messagesWithUserNames)
Nick
  • 1,417
  • 1
  • 14
  • 21
5

Vanilla JS solution

const a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
const a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]

const merge = (arr1, arr2) => {
  const temp = []

  arr1.forEach(x => {
    arr2.forEach(y => {
      if (x.id === y.id) {
        temp.push({ ...x, ...y })
      }
    })
  })

  return temp
}

console.log(merge(a1, a2))
Nick Olay
  • 69
  • 1
  • 4
  • This answer is good except it doesn't handle well if one of the array is empty array. It'll return empty array. – nayiaw Sep 11 '19 at 07:36
4

The lodash implementaiton:

var merged = _.map(a1, function(item) {
    return _.assign(item, _.find(a2, ['id', item.id]));
});

The result:

[  
   {  
      "id":1,
      "name":"test",
      "count":"1"
   },
   {  
      "id":2,
      "name":"test2",
      "count":"2"
   }
]
bora89
  • 3,476
  • 1
  • 25
  • 16
4

Wanted to add this answer which is derived from @daisihi answer above. Main difference is that this uses the spread operator. Also, at the end I remove the id because it was not desirable in the first place.

const a3 = [...a1, ...a2].reduce((acc, x) => {
   acc[x.id] = {...acc[x.id] || {}, ...x};
   return acc;
}, {});

This part was taken from another post. removing a property from a list of objects in an array

const newArray = Object.values(a3).map(({id, ...keepAttrs}) => keepAttrs);
Christian Matthew
  • 4,014
  • 4
  • 33
  • 43
3

Found other solutions failing for some cases, so writing a better one here

const a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
const a2 = [{ id : 3, count : "3"}, { id : 1, count : "1"}, {id : 2, count : "2"}]


const mergeHelper = new Map(a1.map(x => [x.id, x]));

for (const x of a2) {
    if (mergeHelper.has(x.id)) {
        const item = mergeHelper.get(x.id);
        mergeHelper.set(x.id, {...item, ...x});
    } else {
        mergeHelper.set(x.id, x);
    }
}

const mergedList = [...mergeHelper.values()];
// For sorted array 
// const mergedSortedList = [...mergeHelper.values()].sort((a, b) => a.id - b.id);

console.log(mergedList)

Using js Map is way faster than other approaches, helps when array length is huge.

shijin
  • 2,998
  • 1
  • 24
  • 30
2

A working TypeScript version:

export default class Merge {
  static byKey(a1: any[], a2: any[], key: string) {
    const res = a1.concat(a2).reduce((acc, x) => {
      acc[x[key]] = Object.assign(acc[x[key]] || {}, x);
      return acc;
    }, {});

    return Object.entries(res).map(pair => {
      const [, value] = pair;
      return value;
    });
  }
}

test("Merge", async () => {
  const a1 = [{ id: "1", value: "1" }, { id: "2", value: "2" }];
  const a2 = [{ id: "2", value: "3" }];

  expect(Merge.byKey(a1, a2, "id")).toStrictEqual([
    {
      id: "1",
      value: "1"
    },
    { id: "2", value: "3" }
  ]);
});

Jeff Tian
  • 5,210
  • 3
  • 51
  • 71
1

try this

var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]
var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]
let arr3 = a1.map((item, i) => Object.assign({}, item, a2[i]));

console.log(arr3);
Trilok Singh
  • 1,227
  • 12
  • 10
1

How about this?

const mergeArrayObjects = (arr1: any[], arr2: any[], mergeByKey: string): any[] => {
  const updatedArr = [];
  for (const obj of arr1) {
    const arr1ValueInArr2 = arr2.find(
      a => a[mergeByKey] === obj[mergeByKey],
    );
    if (arr1ValueInArr2) {
      updatedArr.push(Object.assign(obj, arr1ValueInArr2));
    } else {
      updatedArr.push(obj);
    }
  }
  const mergeByKeyValuesInArr1 = arr1.map(a => a[mergeByKey]);
  const remainingObjInArr2 = arr2.filter(a => !mergeByKeyValuesInArr1.includes(a[mergeByKey]) )
  return updatedArr.concat(remainingObjInArr2)
}
mnihalk
  • 11
  • 2
0

You can write a simple object merging function like this

function mergeObject(cake, icing) {
    var icedCake = {}, ingredient;
    for (ingredient in cake)
        icedCake[ingredient] = cake[ingredient];
    for (ingredient in icing)
        icedCake[ingredient] = icing[ingredient];
    return icedCake;
}

Next, you need to do use a double-loop to apply it to your data structre

var i, j, a3 = a1.slice();
for (i = 0; i < a2.length; ++i)                // for each item in a2
    for (j = 0; i < a3.length; ++i)            // look at items in other array
        if (a2[i]['id'] === a3[j]['id'])       // if matching id
            a3[j] = mergeObject(a3[j], a2[i]); // merge

You can also use mergeObject as a simple clone, too, by passing one parameter as an empty object.

Paul S.
  • 64,864
  • 9
  • 122
  • 138
0
const a3 = a1.map(it1 => {
   it1.test = a2.find(it2 => it2.id === it1.id).test
   return it1
 })
Cà phê đen
  • 1,883
  • 2
  • 21
  • 20
quy pham
  • 51
  • 4
-1

If you have exactly the same number of items in both array with same ids you could do something like this.

const mergedArr = arr1.map((item, i) => {
  if (item.ID === arr2[i].ID) {
    return Object.assign({}, item, arr2[i]);
  }
});
Charitha Goonewardena
  • 4,418
  • 2
  • 36
  • 38
-2
a1.map(x => a2.find(y => y.id === x.id))
AminFarajzadeh
  • 346
  • 5
  • 17
  • This doesn't merge anything. – gre_gor Jul 24 '22 at 12:44
  • Answer needs supporting information Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](https://stackoverflow.com/help/how-to-answer). – moken Jul 27 '23 at 12:54
-3

None of them worked for me. I wrote own:

const formatteddata=data.reduce((a1,a2)=>{

for (let t=0; t<a1.length; t++)
    {var id1=a1[t].id
            for (let tt=0; tt<a2.length; tt++)
                {var id2=a2[tt].id
                    if(id1==date2)
                      {a1[t]={...a1[t],...a2[tt]}}
                }
    }
return a1

})

works with any amount of arrays of objects in arrays, with varying length and not always coinsciding dates

3cirlces
  • 105
  • 11