JavaScript merging objects by id

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

Answers:

Answer

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"

Answer

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

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

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.

Answer

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

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

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))
Answer
const a3 = a1.map(it1 => {
   it1.test = a2.find(it2 => it2.id === it1.id).test
   return it1
 })
Answer

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

Answer

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.

Answer

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

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

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

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

Answer

Short ES6 solution

const a3 = a1.map(t1 => ({...t1, ...a2.find(t2 => t2.id === t1.id)}))

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.