I wonder if there is a simpler method in lodash to replace an item in a JavaScript collection? (Possible duplicate but I did not understand the answer there:)
I looked at their documentation but could not find anything
My code is:
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
// Can following code be reduced to something like _.XX(arr, {id:1}, {id:1, name: "New Name"});
_.each(arr, function(a, idx){
if(a.id === 1){
arr[idx] = {id:1, name: "Person New Name"};
return false;
}
});
_.each(arr, function(a){
document.write(a.name);
});
Update: The object I'm trying to replace with has many properties like
{id: 1, Prop1: ..., Prop2:..., and so on}
Solution:
Thanks to dfsq but I found a proper solution within lodash that seems to work fine and is pretty neat and I put it in a mixin as well since I've this requirement at many places. JSBin
var update = function(arr, key, newval) {
var match = _.find(arr, key);
if(match)
_.merge(match, newval);
else
arr.push(newval);
};
_.mixin({ '$update': update });
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
_.$update(arr, {id:1}, {id:1, name: "New Val"});
document.write(JSON.stringify(arr));
Faster Solution As pointed out by @dfsq, following is way faster
var upsert = function (arr, key, newval) {
var match = _.find(arr, key);
if(match){
var index = _.indexOf(arr, _.find(arr, key));
arr.splice(index, 1, newval);
} else {
arr.push(newval);
}
};
This question is related to
javascript
lodash
If you're just trying to replace one property, lodash _.find
and _.set
should be enough:
var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
_.set(_.find(arr, {id: 1}), 'name', 'New Person');
Immutable, suitable for ReactJS
:
Assume:
cosnt arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
The updated item is the second and name is changed to Special Person
:
const updatedItem = {id:2, name:"Special Person"};
Hint: the lodash has useful tools but now we have some of them on Ecmascript6+, so I just use map
function that is existed on both of lodash
and ecmascript6+
:
const newArr = arr.map(item => item.id === 2 ? updatedItem : item);
Came across this as well and did it simply that way.
const persons = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
const updatedPerson = {id: 1, name: "new Person Name"}
const updatedPersons = persons.map(person => (
person.id === updated.id
? updatedPerson
: person
))
If wanted we can generalize it
const replaceWhere = (list, predicate, replacement) => {
return list.map(item => predicate(item) ? replacement : item)
}
replaceWhere(persons, person => person.id === updatedPerson.id, updatedPerson)
var arr= [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var index = _.findIndex(arr, {id: 1});
arr[index] = {id: 100, name: 'xyz'}
Seems like the simplest solution would to use ES6's .map
or lodash's _.map
:
var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
// lodash
var newArr = _.map(arr, function(a) {
return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});
// ES6
var newArr = arr.map(function(a) {
return a.id === 1 ? {id: 1, name: "Person New Name"} : a;
});
This has the nice effect of avoiding mutating the original array.
If you're looking for a way to immutably change the collection (as I was when I found your question), you might take a look at immutability-helper, a library forked from the original React util. In your case, you would accomplish what you mentioned via the following:
var update = require('immutability-helper')
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}]
var newArray = update(arr, { 0: { name: { $set: 'New Name' } } })
//=> [{id: 1, name: "New Name"}, {id:2, name:"Person 2"}]
Using lodash unionWith function, you can accomplish a simple upsert to an object. The documentation states that if there is a match, it will use the first array. Wrap your updated object in [ ] (array) and put it as the first array of the union function. Simply specify your matching logic and if found it will replace it and if not it will add it
Example:
let contacts = [
{type: 'email', desc: 'work', primary: true, value: 'email prim'},
{type: 'phone', desc: 'cell', primary: true, value:'phone prim'},
{type: 'phone', desc: 'cell', primary: false,value:'phone secondary'},
{type: 'email', desc: 'cell', primary: false,value:'email secondary'}
]
// Update contacts because found a match
_.unionWith([{type: 'email', desc: 'work', primary: true, value: 'email updated'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)
// Add to contacts - no match found
_.unionWith([{type: 'fax', desc: 'work', primary: true, value: 'fax added'}], contacts, (l, r) => l.type == r.type && l.primary == r.primary)
Not bad variant too)
var arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
var id = 1; //id to find
arr[_.find(arr, {id: id})].name = 'New Person';
[ES6] This code works for me.
let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)
You can do it without using lodash.
let arr = [{id: 1, name: "Person 1"}, {id: 2, name: "Person 2"}];
let newObj = {id: 1, name: "new Person"}
/*Add new prototype function on Array class*/
Array.prototype._replaceObj = function(newObj, key) {
return this.map(obj => (obj[key] === newObj[key] ? newObj : obj));
};
/*return [{id: 1, name: "new Person"}, {id: 2, name: "Person 2"}]*/
arr._replaceObj(newObj, "id")
function findAndReplace(arr, find, replace) {
let i;
for(i=0; i < arr.length && arr[i].id != find.id; i++) {}
i < arr.length ? arr[i] = replace : arr.push(replace);
}
Now let's test performance for all methods:
// TC's first approach_x000D_
function first(arr, a, b) {_x000D_
_.each(arr, function (x, idx) {_x000D_
if (x.id === a.id) {_x000D_
arr[idx] = b;_x000D_
return false;_x000D_
}_x000D_
});_x000D_
}_x000D_
_x000D_
// solution with merge_x000D_
function second(arr, a, b) {_x000D_
const match = _.find(arr, a);_x000D_
if (match) {_x000D_
_.merge(match, b);_x000D_
} else {_x000D_
arr.push(b);_x000D_
}_x000D_
}_x000D_
_x000D_
// most voted solution_x000D_
function third(arr, a, b) {_x000D_
const match = _.find(arr, a);_x000D_
if (match) {_x000D_
var index = _.indexOf(arr, _.find(arr, a));_x000D_
arr.splice(index, 1, b);_x000D_
} else {_x000D_
arr.push(b);_x000D_
}_x000D_
}_x000D_
_x000D_
// my approach_x000D_
function fourth(arr, a, b){_x000D_
let l;_x000D_
for(l=0; l < arr.length && arr[l].id != a.id; l++) {}_x000D_
l < arr.length ? arr[l] = b : arr.push(b);_x000D_
}_x000D_
_x000D_
function test(fn, times, el) {_x000D_
const arr = [], size = 250;_x000D_
for (let i = 0; i < size; i++) {_x000D_
arr[i] = {id: i, name: `name_${i}`, test: "test"};_x000D_
}_x000D_
_x000D_
let start = Date.now();_x000D_
_.times(times, () => {_x000D_
const id = Math.round(Math.random() * size);_x000D_
const a = {id};_x000D_
const b = {id, name: `${id}_name`};_x000D_
fn(arr, a, b);_x000D_
});_x000D_
el.innerHTML = Date.now() - start;_x000D_
}_x000D_
_x000D_
test(first, 1e5, document.getElementById("first"));_x000D_
test(second, 1e5, document.getElementById("second"));_x000D_
test(third, 1e5, document.getElementById("third"));_x000D_
test(fourth, 1e5, document.getElementById("fourth"));
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>_x000D_
<div>_x000D_
<ol>_x000D_
<li><b id="first"></b> ms [TC's first approach]</li>_x000D_
<li><b id="second"></b> ms [solution with merge]</li>_x000D_
<li><b id="third"></b> ms [most voted solution]</li>_x000D_
<li><b id="fourth"></b> ms [my approach]</li>_x000D_
</ol>_x000D_
<div>
_x000D_
As the time passes you should embrace a more functional approach in which you should avoid data mutations and write small, single responsibility functions. With the ECMAScript 6 standard, you can enjoy functional programming paradigm in JavaScript with the provided map
, filter
and reduce
methods. You don't need another lodash, underscore or what else to do most basic things.
Down below I have included some proposed solutions to this problem in order to show how this problem can be solved using different language features:
Using ES6 map:
const replace = predicate => replacement => element =>_x000D_
predicate(element) ? replacement : element_x000D_
_x000D_
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];_x000D_
const predicate = element => element.id === 1_x000D_
const replacement = { id: 100, name: 'New object.' }_x000D_
_x000D_
const result = arr.map(replace (predicate) (replacement))_x000D_
console.log(result)
_x000D_
Recursive version - equivalent of mapping:
Requires destructuring and array spread.
const replace = predicate => replacement =>_x000D_
{_x000D_
const traverse = ([head, ...tail]) =>_x000D_
head_x000D_
? [predicate(head) ? replacement : head, ...tail]_x000D_
: []_x000D_
return traverse_x000D_
}_x000D_
_x000D_
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];_x000D_
const predicate = element => element.id === 1_x000D_
const replacement = { id: 100, name: 'New object.' }_x000D_
_x000D_
const result = replace (predicate) (replacement) (arr)_x000D_
console.log(result)
_x000D_
When the final array's order is not important you can use an object
as a HashMap data structure. Very handy if you already have keyed collection as an object
- otherwise you have to change your representation first.
Requires object rest spread, computed property names and Object.entries.
const replace = key => ({id, ...values}) => hashMap =>_x000D_
({_x000D_
...hashMap, //original HashMap_x000D_
[key]: undefined, //delete the replaced value_x000D_
[id]: values //assign replacement_x000D_
})_x000D_
_x000D_
// HashMap <-> array conversion_x000D_
const toHashMapById = array =>_x000D_
array.reduce(_x000D_
(acc, { id, ...values }) => _x000D_
({ ...acc, [id]: values })_x000D_
, {})_x000D_
_x000D_
const toArrayById = hashMap =>_x000D_
Object.entries(hashMap)_x000D_
.filter( // filter out undefined values_x000D_
([_, value]) => value _x000D_
) _x000D_
.map(_x000D_
([id, values]) => ({ id, ...values })_x000D_
)_x000D_
_x000D_
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];_x000D_
const replaceKey = 1_x000D_
const replacement = { id: 100, name: 'New object.' }_x000D_
_x000D_
// Create a HashMap from the array, treating id properties as keys_x000D_
const hashMap = toHashMapById(arr)_x000D_
console.log(hashMap)_x000D_
_x000D_
// Result of replacement - notice an undefined value for replaced key_x000D_
const resultHashMap = replace (replaceKey) (replacement) (hashMap)_x000D_
console.log(resultHashMap)_x000D_
_x000D_
// Final result of conversion from the HashMap to an array_x000D_
const result = toArrayById (resultHashMap)_x000D_
console.log(result)
_x000D_
You can also use findIndex and pick to achieve the same result:
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
var data = {id: 2, name: 'Person 2 (updated)'};
var index = _.findIndex(arr, _.pick(data, 'id'));
if( index !== -1) {
arr.splice(index, 1, data);
} else {
arr.push(data);
}
If the insertion point of the new object does not need to match the previous object's index then the simplest way to do this with lodash is by using _.reject
and then pushing new values in to the array:
var arr = [
{ id: 1, name: "Person 1" },
{ id: 2, name: "Person 2" }
];
arr = _.reject(arr, { id: 1 });
arr.push({ id: 1, name: "New Val" });
// result will be: [{ id: 2, name: "Person 2" }, { id: 1, name: "New Val" }]
If you have multiple values that you want to replace in one pass, you can do the following (written in non-ES6 format):
var arr = [
{ id: 1, name: "Person 1" },
{ id: 2, name: "Person 2" },
{ id: 3, name: "Person 3" }
];
idsToReplace = [2, 3];
arr = _.reject(arr, function(o) { return idsToReplace.indexOf(o.id) > -1; });
arr.push({ id: 3, name: "New Person 3" });
arr.push({ id: 2, name: "New Person 2" });
// result will be: [{ id: 1, name: "Person 1" }, { id: 3, name: "New Person 3" }, { id: 2, name: "New Person 2" }]
Source: Stackoverflow.com