21

I want to update (replace) the objects in my array with the objects in another array. Each object has the same structure. e.g.

var origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];
var updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];
// desired result:
NEWArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];

I've tried concat() & Underscore's _.uniq function, but it always dumps the newer object & returns, essentially, the original array.

Is there a way to overwrite (replace) origArr with the objects in updatingArr -- matching on the name property?

2
  • 1
    Is that what you are meaning? jsfiddle.net/4p98w3su not sure I understood the question. Commented May 5, 2016 at 18:35
  • All good answers here; I like yours the best, using splice. If you want to convert this to an Answer, I'll mark as Accepted. Thanks. Commented May 5, 2016 at 18:54

14 Answers 14

21

I came here looking for exactly this, saw @Gruff Bunny 's technique and wondered if 'lodash' wouldn't perhaps be a superior option even to 'underscore'?

Lo and behold :

let result = _.unionBy(updatingArr, origArr, 'name');
Sign up to request clarification or add additional context in comments.

1 Comment

While Underscore is much smaller than Lodash, you can still get the same result in a single line using concat, groupBy, map and last. You can also add unionBy to Underscore in just five lines of code. See this answer.
11

You could use Array#map in combination with Array#reduce

var origArr = [{ name: 'Trump', isRunning: true }, { name: 'Cruz', isRunning: true }, { name: 'Kasich', isRunning: true }],
    updatingArr = [{ name: 'Cruz', isRunning: false }, { name: 'Kasich', isRunning: false }],
    NEWArr = origArr.map(function (a) {
        return this[a.name] || a;
    }, updatingArr.reduce(function (r, a) {
        r[a.name] = a;
        return r;
    }, Object.create(null)));

document.write('<pre>' + JSON.stringify(NEWArr, 0, 4) + '</pre>');

UPDATE 2022

Using an object with name as hash and mapping the original array by taking the update from hash table or the original object.

const
    origArr = [{ name: 'Trump', isRunning: true }, { name: 'Cruz', isRunning: true }, { name: 'Kasich', isRunning: true }],
    updatingArr = [{ name: 'Cruz', isRunning: false }, { name: 'Kasich', isRunning: false }],
    updates = Object.fromEntries(updatingArr.map(o => [o.name, o])),
    result = origArr.map(o => updates[o.name] || o);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Another approach by using Map.

This approach works for objects who are only in the updating array as well.

const
    origArr = [{ name: 'Trump', isRunning: true }, { name: 'Cruz', isRunning: true }, { name: 'Kasich', isRunning: true }],
    updatingArr = [{ name: 'Cruz', isRunning: false }, { name: 'Kasich', isRunning: false }],
    result = Array.from([...origArr, ...updatingArr]
        .reduce((m, o) => m.set(o.name, o), new Map)
        .values()
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

4 Comments

i need some explainer for this, please... :/
@A.Rabus, this code is acutally using a hash table for udate values and maps either the updates values or the original value. actually, i would separate the two steps into two parts and do not use this anymore.
How to achieve if from updatingArr id is not existing on the origArr. Just push the updatingArr elements to origArr
This is amazing! Congratulations!
9

Using a double for loop and splice you can do it like so:

for(var i = 0, l = origArr.length; i < l; i++) {
    for(var j = 0, ll = updatingArr.length; j < ll; j++) {
        if(origArr[i].name === updatingArr[j].name) {
            origArr.splice(i, 1, updatingArr[j]);
            break;
        }
    }
}

Example here

1 Comment

with this being the accepted answer. How to achieve if from updatingArr id is not existing on the origArr. Just push the updatingArr elements to origArr.
3
const origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];

const updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];

let hash = {};

for(let i of origArr.concat(updatingArr)) {
  if(!hash[i.name]) {
    hash[i.name] = i;
  }
}

let newArr = Object.values(hash);


console.log(newArr);

Comments

2

You can use a hash which gives the index by name, and Object.assign to update.

var hash = origArr.reduce(function(hash, obj, index) {
  hash[obj.name] = index;
  return hash;
}, Object.create(null));
for(var obj of updatingArr) {
  Object.assign(origArr[hash[obj.name]], obj);
}

Comments

2

You can give this a try.

var origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];
var updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];

var origLength = origArr.length;
var updatingLength = updatingArr.length;

//Traverse the original array and replace only if the second array also has the same value
for(i = origLength-1; i >= 0; i--) {
    for(j = updatingLength -1; j >= 0; j--) {
    if(origArr[i].name === updatingArr[j].name) {
        origArr[i] = updatingArr[j];
    }
  }
}

console.log(origArr);

1 Comment

traversal is done in the reverse order since it is least time consuming, according to tests conducted by jspref
2

Here's a solution using underscore:

var result = _.map(origArr, function(orig){
    return _.extend(orig, _.findWhere(updatingArr, {name: orig.name}));
});

1 Comment

More compact and efficient solution using Underscore, which also adds new entries on update: stackoverflow.com/a/70474201/1166087
2

This version lets you define the selector that defines an object as duplicate.

  • forEach iterates over the new data
  • findIndex returns an index >= 0 if two selectors are equal. If none are equal, it returns -1
  • If there is a duplicate, we use slice to replace the original by the new.
  • If there's no duplicate, we push it into the original array.

const origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];

const updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];

const mergeArrayOfObjects = (original, newdata, selector = 'key') => {
	newdata.forEach(dat => {
		const foundIndex = original.findIndex(ori => ori[selector] == dat[selector]);
		if (foundIndex >= 0) original.splice(foundIndex, 1, dat);
        else original.push(dat);
	});

	return original;
};

const result = mergeArrayOfObjects(origArr, updatingArr, "name")
console.log('RESULT -->', result)

Comments

2

In ES6 you can use the object Map like this...

let map = new Map();
let origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];
let updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];

// Concating arrays with duplicates
let NEWArr = origArr.concat(updatingArr);

// Removing duplicates items
NEWArr.forEach(item => {
  if(!map.has(item.name)){
    map.set(item.name, item);
  }
});

Array.from(map.values());

Remember: the Map object need in unique key, in this case I used the name.

2 Comments

Thx, this is short and clean!
I do not think this is correct. Because the result is {name: 'Trump', isRunning: true}, {name: 'Cruz', isRunning: true}, {name: 'Kasich', isRunning: true} which I think is not what you want.
1

This will do what you need:

var origArr = [
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
];

var updatingArr = [
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
];

for (var i = 0; i < updatingArr.length; ++i) {
  var updateItem = updatingArr[i];
  for (var j = 0; j < origArr.length; ++j) {
    var origItem = origArr[j];
    if (origItem.name == updateItem.name) {
      origItem.isRunning = updateItem.isRunning;
      break;    
    }
  }
}

document.write('<pre>' + JSON.stringify(origArr, 0, 4) + '</pre>');

Comments

1

Same as @gevorg answer, but you may want to also add a new object to the original array if no matches are found.

let combinedEvents = origEvents;
for(let i =0; i< newEvents.length; i++){
  let newEvent = newEvents[i];
  for(let j =0; j< origEvents.length; j++){
    let origEvent = origEvents[j];
    if(newEvent.events_id == origEvent.events_id){
      combinedEvents.splice(j,1, newEvent);
      break;
    } else if(j === origEvents.length - 1){
      combinedEvents.push(newEvent);
      break;
    }
  }
}

Comments

0

Backbone's collections are well equipped for scenarios like this one.

First, we define a model type that knows to look for the name property:

import { Model } from 'backbone';

const President = Model.extend({ idAttribute: 'name' });

Then, we put the initial array in a collection that uses the above model:

import { Collection } from 'backbone';

const presidents = new Collection(origArr, { model: President });

Now, we can run presidents.set as often as we want, and it will merge the new representation with the old values:

presidents.set(updateArr);

presidents.get('Cruz').get('isRunning'); // false
presidents.toJSON(); // NEWArr

Putting it all together in a runnable snippet:

const { Model, Collection } = Backbone;

const President = Model.extend({ idAttribute: 'name' });

const presidents = new Collection([
  {name: 'Trump', isRunning: true},
  {name: 'Cruz', isRunning: true},
  {name: 'Kasich', isRunning: true}
], { model: President });

presidents.set([
  {name: 'Cruz', isRunning: false},
  {name: 'Kasich', isRunning: false}
]);

console.log(presidents.get('Cruz').get('isRunning'));
console.log(presidents.toJSON());
<script src="https://underscorejs.org/underscore-umd-min.js"></script>
<script src="https://backbonejs.org/backbone-min.js"></script>

1 Comment

Single-line pure Underscore solution here: stackoverflow.com/a/70474201/1166087
0

While Underscore does not have a direct equivalent to Lodash's unionBy, you can still get the same result in an efficient oneliner using concat, groupBy, map and last:

import { groupBy, map, last } from 'underscore';

var NEWArr = map(groupBy(origArr.concat(updateArr), 'name'), last);

Equivalent, slightly lengthier but cleaner notation using chain:

import { chain } from 'underscore';

var NEWArr = chain(origArr).concat(updateArr).groupBy('name').map(last).value();

In fact, you can define your own unionBy using the above recipe and add it to Underscore yourself:

import _, { groupBy, map, first } from 'underscore';

function unionBy(...args) {
    const iteratee = _.iteratee(args.pop());
    const candidates = [].concat.apply(null, args);
    return map(groupBy(candidates, iteratee), first);
}

_.mixin({ unionBy }); // unionBy can now be used in chaining

Note that we are now using first instead of last, because the semantics of Lodash's unionBy dictate that the first occurrence wins.

1 Comment

While not asked in the original question, Backbone arguably has the best solution for updating data. See stackoverflow.com/a/70474027/1166087.
-1

Try this approach with ES-6 Set Data Structure: const result = [...new Set([...origArr, ...updatingArr])]

1 Comment

This will only work for the flat lists. Won't work for this scenario.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.