Reputation: 2619
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?
Upvotes: 20
Views: 58797
Reputation: 71
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);
Upvotes: 3
Reputation: 386883
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; }
Upvotes: 10
Reputation: 4380
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.
Upvotes: 0
Reputation: 4380
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>
Upvotes: 0
Reputation: 69
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
.
Upvotes: 2
Reputation: 4170
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;
}
}
}
Upvotes: 1
Reputation: 160
Try this approach with ES-6 Set Data Structure:
const result = [...new Set([...origArr, ...updatingArr])]
Upvotes: -1
Reputation: 1009
This version lets you define the selector
that defines an object as duplicate.
>= 0
if two selectors are equal. If none are equal, it returns -1
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)
Upvotes: 2
Reputation: 2121
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');
Upvotes: 21
Reputation: 1167
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
Upvotes: 9
Reputation: 27986
Here's a solution using underscore:
var result = _.map(origArr, function(orig){
return _.extend(orig, _.findWhere(updatingArr, {name: orig.name}));
});
Upvotes: 2
Reputation: 910
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);
Upvotes: 2
Reputation: 288710
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);
}
Upvotes: 2
Reputation: 5065
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>');
Upvotes: 1