Replacing objects in array
Asked Answered
H

18

164

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?

Highsmith answered 2/6, 2016 at 7:27 Comment(1)
Does this answer your question? JavaScript - merge two arrays of objects and de-duplicate based on property valueSediment
S
288

You can use Array#map with Array#find.

arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);

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

var res = arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);

console.log(res);

Here, arr2.find(o => o.id === obj.id) will return the element i.e. object from arr2 if the id is found in the arr2. If not, then the same element in arr1 i.e. obj is returned.

Slaveholder answered 2/6, 2016 at 7:30 Comment(12)
@Highsmith No. it'll only work in latest browsers. You can see Specifications and for older browsers use polyfillSlaveholder
Sometimes we come across very instructive and useful code; this is one of those times. ThanksBoredom
This is a very inefficient way of achieving the goal since find method will iterate the entire array each time its called.Separative
@JavaBanana What do you suggest?Slaveholder
Would have been great if also included ES5 syntax.Verst
@MuhammadQasim Here is pure ES5 syntax: arr1.map(function(obj) { var foundObj = arr2.filter(function(o) { return o.id === obj.id; }); return foundObj.length ? foundObj[0] : obj; });. You could also use find polyfill instead of filter.Slaveholder
Mind that you may need to convert IDs or Numbers if you try to match those. I had an issue with {id: 123} and {id: "123"}. Once I used id.toString() this code worked perfectly fine. Thanks for sharing.Der
if Id didn't match or not found, Can we add that item into the arr1?Penny
@Penny You need to first use find to check if the item exists in the array, if not add the item to the array.Slaveholder
This is not so readable or debuggable, here a more transparent approach: https://mcmap.net/q/149436/-replacing-objects-in-arrayKunstlied
Yes it takes some time to get used to concise arrow syntax. Once you understand it, you’ll always use it and will be readable.Slaveholder
how will i able to push the remaining element from arr2 if id is not existing on the arr1? @SlaveholderOverride
L
12

There is always going to be a good debate on time vs space, however these days I've found using space is better for the long run.. Mathematics aside let look at a one practical approach to the problem using hashmaps, dictionaries, or associative array's whatever you feel like labeling the simple data structure..

    var marr2 = new Map(arr2.map(e => [e.id, e]));
    arr1.map(obj => marr2.has(obj.id) ? marr2.get(obj.id) : obj);

I like this approach because though you could argue with an array with low numbers you are wasting space because an inline approach like @Tushar approach performs indistinguishably close to this method. However I ran some tests and the graph shows how performant in ms both methods perform from n 0 - 1000. You can decide which method works best for you, for your situation but in my experience users don't care to much about small space but they do care about small speed.


Performance Measurement


Here is my performance test I ran for source of data

var n = 1000;
var graph = new Array();
for( var x = 0; x < n; x++){
  var arr1s = [...Array(x).keys()];
  var arr2s = arr1s.filter( e => Math.random() > .5);
  var arr1 = arr1s.map(e => {return {id: e, name: 'bill'}});
  var arr2 = arr2s.map(e => {return {id: e, name: 'larry'}});
  // Map 1
  performance.mark('p1s');
  var marr2 = new Map(arr2.map(e => [e.id, e]));
  arr1.map(obj => marr2.has(obj.id) ? marr2.get(obj.id) : obj);
  performance.mark('p1e');
  // Map 2
  performance.mark('p2s');
  arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
  performance.mark('p2e');
  graph.push({ x: x, r1: performance.measure('HashMap Method', 'p1s', 'p1e').duration, r2: performance.measure('Inner Find', 'p2s','p2e').duration});
}
Luce answered 11/4, 2021 at 7:26 Comment(2)
How do you display that graph from your data?Showoff
For this I threw the data into an excel spreadsheet and graphed it, I suppose I could of used some fancy graph library but I am lazyLuce
Q
9

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?

Quent answered 27/9, 2019 at 2:16 Comment(2)
Doesn't work in Internet Explorer 11. There's a polyfill though: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Hoch
I needed something were I have an array and I want to push another object onto that array if the object is not present in the array. I was using some to find if the object was present or not, if not I would push the object, but now my object is getting updated and should be updated on the arrray too, but using some it cant happen.Brickbat
M
8

I'd like to suggest another solution:

const objectToReplace = this.array.find(arrayItem => arrayItem.id === requiredItem.id);
Object.assign(objectToReplace, newObject);
Mccaslin answered 6/5, 2022 at 16:24 Comment(0)
C
5

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:

function mergeById(arr) {
  return {
    with: function(arr2) {
      return _.map(arr, item => {
        return _.find(arr2, obj => obj.id === item.id) || item
      })
    }
  }
}

var result = mergeById([{id:'124',name:'qqq'}, 
           {id:'589',name:'www'}, 
           {id:'45',name:'eee'},
           {id:'567',name:'rrr'}])
    .with([{id:'124',name:'ttt'}, {id:'45',name:'yyy'}])

console.log(result);
<script src="https://raw.githubusercontent.com/lodash/lodash/4.13.1/dist/lodash.js"></script>
Cerargyrite answered 2/6, 2016 at 7:54 Comment(0)
D
5

I like to go through arr2 with foreach() and use findIndex() for checking for occurrence in arr1:

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

arr2.forEach(element => {
            const itemIndex = arr1.findIndex(o => o.id === element.id);
            if(itemIndex > -1) {
                arr1[itemIndex] = element;
            } else {
                arr1 = arr1.push(element);
            }       
        });
    
console.log(arr1)
Defeasance answered 6/11, 2021 at 20:0 Comment(0)
R
4

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"}]
Rufus answered 6/7, 2018 at 10:1 Comment(1)
So slick! Using it now.Domineca
R
3

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));
})
Razor answered 2/9, 2020 at 20:36 Comment(2)
Will this mutate the array?Sledge
@Sledge No it won't, since the reduce function does not mutate. See reduceRazor
K
2

Here a more transparent approach. I find the oneliners harder to read and harder to debug.

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;
    }
}
Kunstlied answered 12/9, 2020 at 8:58 Comment(0)
W
2
// here find all the items that are not it the arr1
const temp = arr1.filter(obj1 => !arr2.some(obj2 => obj1.id === obj2.id))
// then just concat it
arr1 = [...temp, ...arr2]
Winthrop answered 22/5, 2021 at 3:53 Comment(0)
S
0

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

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

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

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
Shorthanded answered 3/6, 2016 at 11:48 Comment(0)
P
0

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

Porfirioporgy answered 2/11, 2017 at 18:25 Comment(3)
fyi, it does not appear to be working in the fiddle.Boredom
I forgot to add console.log in the fiddle. I updated it and it should now print out the results.Porfirioporgy
Still doesn't appear to work, but it does work in my application. That's the main thing :-)Boredom
G
0
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);
Grill answered 20/7, 2018 at 15:55 Comment(0)
S
0

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.`*/
Staton answered 11/2, 2020 at 2:5 Comment(0)
B
0

The accepted answer using array.map is correct but you have to remember to assign it to another variable since array.map doesnt change original array, it actually creates a new array.

//newArr contains the mapped array from arr2 to arr1. 
//arr1 still contains original value

var newArr = arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
Buskus answered 20/5, 2021 at 4:38 Comment(0)
G
0

Array.prototype.update = function(...args) {
  return this.map(x=>args.find((c)=>{return c.id===x.id})  || x)    
}

const result = 
        [
            {id:'1',name:'test1'}, 
            {id:'2',name:'test2'}, 
            {id:'3',name:'test3'},
            {id:'4',name:'test4'}
        ]
        .update({id:'1',name:'test1.1'}, {id:'3',name:'test3.3'})

console.log(result)
Giulia answered 8/4, 2022 at 0:32 Comment(0)
S
0

Efficient JavaScript Code for Merging and Updating Records using key

There are already some good answers on the forum, but I am still posting my refined solution for merging and updating records in JavaScript.

const existingItems = [
  { id: 1, name: 'Item A', category: 'Category 1' },
  { id: 2, name: 'Item B', category: 'Category 2' },
  { id: 3, name: 'Item C', category: 'Category 1' },
  // Add more source items as needed
];

// Usually new data from the server
const newItems = [
  { id: 3, name: 'Item C', category: 'Category 3' },
  { id: 4, name: 'Item D', category: 'Category 4' },
  // Add more new items as needed
];

let key = "id";

let newList = [...new Map([...existingItems, ...newItems].map((item) => [item[key], item])).values()];

console.log(newList);
Sse answered 8/12, 2023 at 12:46 Comment(0)
S
-3

This is how I do it in TypeScript:

const index = this.array.indexOf(this.objectToReplace);
this.array[index] = newObject;
Shogunate answered 4/6, 2020 at 19:51 Comment(4)
You can't find index of an object in javascript in this way.Nonconformity
@PranKumarSarkar sure you can, it will just match against the objects reference jsfiddle.net/hutchthehippo/g9a6wro5/22Luce
If the objects in array is of same type then you can. But in my case, my array contained json objects, and each json object contained different key value pairs, so it was giving wrong result.Nonconformity
@PranKumarSarkar Sure, i meant for same object types.Shogunate

© 2022 - 2024 — McMap. All rights reserved.