83

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

6
  • 1
    So what you actually want to do is merge the objects. Commented Oct 20, 2013 at 16:46
  • Your syntax is invalid. Do you have a legit example? Commented Oct 20, 2013 at 16:49
  • See stackoverflow.com/questions/10688569/javascript-extend-array Commented Oct 20, 2013 at 16:58
  • Closely related to stackoverflow.com/q/37057746/1166087. Commented Dec 27, 2021 at 11:39
  • 1
    @dippas OP wants to merge objects based on ID, not just merge arrays. And de-duplication in those answers won't work on objects anyway. Commented Jul 24, 2022 at 12:26

18 Answers 18

69

Short ES6 solution

const a3 = a1.map(t1 => ({...t1, ...a2.find(t2 => t2.id === t1.id)})) 
Sign up to request clarification or add additional context in comments.

3 Comments

It doesn't merge ids that are only found in a2
@Gabriel I think the requirement was to have two lists that share id but have different properties.
This is not working for multiple cases.
46

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"

9 Comments

This won't work if a2 has more items in the array than a1.
@CAOakley - yes it will. What makes you think it doesn't work?
@AndreaPuddu _.unionBy is in Lodash, not Underscore, but good to know!
@4castle hehe lodash, of course... anyone still using underscore? :P
Lodash has deprecated _.findWhere in favor of _.find with an iteratee so use _.find(a2, { id: item.id }) instead.
|
29

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()); 

1 Comment

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: stackoverflow.com/a/70474201/1166087.
18

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} 

5 Comments

This is not an answer to the question. Please do check the expected result and improve your answer, or remove it.
@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.
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.
I don't think this works if the lists aren't in the same order.
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.
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.

1 Comment

If you wish to skip using lodash, simple way would be Object.values or Object.keys(a3).map(k => a3[k])
5

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" } ] 

1 Comment

This won't work if a2 has more items in the array than a1
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) 

Comments

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)) 

1 Comment

This answer is good except it doesn't handle well if one of the array is empty array. It'll return empty array.
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); 

Comments

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.

Comments

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" } ]); }); 

Comments

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); 

Comments

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) } 

Comments

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.

Comments

0
const a3 = a1.map(it1 => { it1.test = a2.find(it2 => it2.id === it1.id).test return it1 }) 

1 Comment

what is the test thing?
0

You can use Object.groupBy to do the grouping, and then use Array#reduce to merge its content.

It worth noting that Object.groupBy hasn't been widely suported yet. So it might not be a good idea to use in production environment by the time this answer has been posted.

var a1 = [{ id : 1, name : "test"}, { id : 2, name : "test2"}]; var a2 = [{ id : 1, count : "1"}, {id : 2, count : "2"}]; var merged = Object.values(Object.groupBy([...a1, ...a2], ({ id }) => id)).map(e => e.reduce((acc, cur) => ({ ...acc, ...cur })) ); console.log(merged);

Comments

-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]); } }); 

Comments

-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

2 Comments

again, this doesn't answer the question
This doesn't work at all. What is data? Why is there date2? What's even the point of this improper use of reduce? This code is just nonsense.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.