[javascript] Replacing objects in array

I have this javascript object:

var arr1 = [{id:'124',name:'qqq'}, 
           {id:'589',name:'www'}, 
           {id:'45',name:'eee'},
           {id:'567',name:'rrr'}]

var arr2 = [{id:'124',name:'ttt'}, 
           {id:'45',name:'yyy'}]

I need to replace objects in arr1 with items from arr2 with same id.

So here is the result I want to get:

var arr1 = [{id:'124',name:'ttt'}, 
           {id:'589',name:'www'}, 
           {id:'45',name:'yyy'},
           {id:'567',name:'rrr'}]

How can I implement it using javascript?

This question is related to javascript lodash

The answer is


Here a transparent approach, instead of an unreadable and undebuggable oneliner:

export class List {
    static replace = (object, list) => {
        let newList = [];
        list.forEach(function (item) {
            if (item.id === object.id) {
                newList.push(object);
            } else {
                newList.push(item);
            }
        });
        return newList;
    }
}

Thanks to ES6 we can made it with easy way -> for example on util.js module ;))).

  1. Merge 2 array of entity

    export const mergeArrays = (arr1, arr2) => 
       arr1 && arr1.map(obj => arr2 && arr2.find(p => p.id === obj.id) || obj);
    

gets 2 array and merges it.. Arr1 is main array which is priority is high on merge process

  1. Merge array with same type of entity

    export const mergeArrayWithObject = (arr, obj) => arr && arr.map(t => t.id === obj.id ? obj : t);
    

it merges the same kind of array of type with some kind of type for

example: array of person ->

[{id:1, name:"Bir"},{id:2, name: "Iki"},{id:3, name:"Uc"}]   
second param Person {id:3, name: "Name changed"}   

result is

[{id:1, name:"Bir"},{id:2, name: "Iki"},{id:3, name:"Name changed"}]

What's wrong with Object.assign(target, source) ?

enter image description here

Arrays are still type object in Javascript, so using assign should still reassign any matching keys parsed by the operator as long as matching keys are found, right?


Since you're using Lodash you could use _.map and _.find to make sure major browsers are supported.

In the end I would go with something like:

_x000D_
_x000D_
function mergeById(arr) {_x000D_
  return {_x000D_
    with: function(arr2) {_x000D_
      return _.map(arr, item => {_x000D_
        return _.find(arr2, obj => obj.id === item.id) || item_x000D_
      })_x000D_
    }_x000D_
  }_x000D_
}_x000D_
_x000D_
var result = mergeById([{id:'124',name:'qqq'}, _x000D_
           {id:'589',name:'www'}, _x000D_
           {id:'45',name:'eee'},_x000D_
           {id:'567',name:'rrr'}])_x000D_
    .with([{id:'124',name:'ttt'}, {id:'45',name:'yyy'}])_x000D_
_x000D_
console.log(result);
_x000D_
<script src="https://raw.githubusercontent.com/lodash/lodash/4.13.1/dist/lodash.js"></script>
_x000D_
_x000D_
_x000D_


function getMatch(elem) {
    function action(ele, val) {
        if(ele === val){ 
            elem = arr2[i]; 
        }
    }

    for (var i = 0; i < arr2.length; i++) {
        action(elem.id, Object.values(arr2[i])[0]);
    }
    return elem;
}

var modified = arr1.map(getMatch);

This is how I do it in TypeScript:

const index = this.array.indexOf(this.objectToReplace);
this.array[index] = newObject;

I am only submitting this answer because people expressed concerns over browsers and maintaining the order of objects. I recognize that it is not the most efficient way to accomplish the goal.

Having said this, I broke the problem down into two functions for readability.

// The following function is used for each itertion in the function updateObjectsInArr
const newObjInInitialArr = function(initialArr, newObject) {
  let id = newObject.id;
  let newArr = [];
  for (let i = 0; i < initialArr.length; i++) {
    if (id === initialArr[i].id) {
      newArr.push(newObject);
    } else {
      newArr.push(initialArr[i]);
    }
  }
  return newArr;
};

const updateObjectsInArr = function(initialArr, newArr) {
    let finalUpdatedArr = initialArr;  
    for (let i = 0; i < newArr.length; i++) {
      finalUpdatedArr = newObjInInitialArr(finalUpdatedArr, newArr[i]);
    }

    return finalUpdatedArr
}

const revisedArr = updateObjectsInArr(arr1, arr2);

jsfiddle


Considering that the accepted answer is probably inefficient for large arrays, O(nm), I usually prefer this approach, O(2n + 2m):

function mergeArrays(arr1 = [], arr2 = []){
    //Creates an object map of id to object in arr1
    const arr1Map = arr1.reduce((acc, o) => {
        acc[o.id] = o;
        return acc;
    }, {});
    //Updates the object with corresponding id in arr1Map from arr2, 
    //creates a new object if none exists (upsert)
    arr2.forEach(o => {
        arr1Map[o.id] = o;
    });

    //Return the merged values in arr1Map as an array
    return Object.values(arr1Map);
}

Unit test:

it('Merges two arrays using id as the key', () => {
   var arr1 = [{id:'124',name:'qqq'}, {id:'589',name:'www'}, {id:'45',name:'eee'}, {id:'567',name:'rrr'}];
   var arr2 = [{id:'124',name:'ttt'}, {id:'45',name:'yyy'}];
   const actual = mergeArrays(arr1, arr2);
   const expected = [{id:'124',name:'ttt'}, {id:'589',name:'www'}, {id:'45',name:'yyy'}, {id:'567',name:'rrr'}];
   expect(actual.sort((a, b) => (a.id < b.id)? -1: 1)).toEqual(expected.sort((a, b) => (a.id < b.id)? -1: 1));
})

I went with this, because it makes sense to me. Comments added for readers!

masterData = [{id: 1, name: "aaaaaaaaaaa"}, 
        {id: 2, name: "Bill"},
        {id: 3, name: "ccccccccc"}];

updatedData = [{id: 3, name: "Cat"},
               {id: 1, name: "Apple"}];

updatedData.forEach(updatedObj=> {
       // For every updatedData object (dataObj), find the array index in masterData where the IDs match.
       let indexInMasterData = masterData.map(masterDataObj => masterDataObj.id).indexOf(updatedObj.id); // First make an array of IDs, to use indexOf().
       // If there is a matching ID (and thus an index), replace the existing object in masterData with the updatedData's object.
       if (indexInMasterData !== undefined) masterData.splice(indexInMasterData, 1, updatedObj);
});

/* masterData becomes [{id: 1, name: "Apple"}, 
                       {id: 2, name: "Bill"},
                       {id: 3, name: "Cat"}];  as you want.`*/

If you don't care about the order of the array, then you may want to get the difference between arr1 and arr2 by id using differenceBy() and then simply use concat() to append all the updated objects.

var result = _(arr1).differenceBy(arr2, 'id').concat(arr2).value();

_x000D_
_x000D_
var arr1 = [{_x000D_
  id: '124',_x000D_
  name: 'qqq'_x000D_
}, {_x000D_
  id: '589',_x000D_
  name: 'www'_x000D_
}, {_x000D_
  id: '45',_x000D_
  name: 'eee'_x000D_
}, {_x000D_
  id: '567',_x000D_
  name: 'rrr'_x000D_
}]_x000D_
_x000D_
var arr2 = [{_x000D_
  id: '124',_x000D_
  name: 'ttt'_x000D_
}, {_x000D_
  id: '45',_x000D_
  name: 'yyy'_x000D_
}];_x000D_
_x000D_
var result = _(arr1).differenceBy(arr2, 'id').concat(arr2).value();_x000D_
_x000D_
console.log(result);
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
_x000D_
_x000D_
_x000D_