Update Multiple Objects in Nested Arrays - javascript

I have an array of multi dimensional objects:
var arr = [
{
id: '10c',
name: 'item 1'
children: [
{id: '11v', name: 'Item 1 child 1'},
{id: '12c', name: 'Item 1 child 2'}
]
},
{
id: '13v',
name: 'item 2'
children: [
{id: '26e', name: 'Item 2 child 1'},
{id: '7a', name: 'Item 2 child 2'}
]
}
]
and another object of data:
var array = [
{id: '12c', name: 'New name 1'},
{id: '26e', name: 'New name 2'},
{id: '11v', name: 'New name 3'},
];
If I want to update the name value of the respective objects in arr, based on the id value in array, how would be the best way of doing that?
arr might be more than 2 levels deep, so I would like to be able to do it without having to nest multiple forEach

Try this:
function update(items, id, name) {
var item;
for (var i = 0; i < items.length; i++) {
item = items[i];
if (item.id === id) {
item.name = name;
return;
}
if (item.children) {
update(item.children, id, name);
}
}
}
Then:
update(arr, '11v', 'test');
Updating by array:
array.forEach(function (item) {
update(arr, item.id, item.name);
});

Use a recursive solution and make use of the inherent mutability of Javascript.
First build up a flat map representation of all the objects.
function createObjectMap(array) {
var map = {};
array.forEach(function(obj) {
map[obj.id] = obj;
if(obj.children) {
Object.assign(map, createObjectMap(obj.children));
}
});
return map;
}
Now you've got a map of id -> object you can use. They reference the same mutable objects, so you can mutate them in place and know that they'll also update in the original data structure.
function update(map, updates) {
updates.forEach(function(update) {
map[update.id].name = update.name;
});
}
This conversion to O(1) lookup time means you don't have to write any searching logic.

You could use angular $filter to do the same
//to get element
function getFiltered(collection, obj){
var filtered = $filter('filter')(collection, obj, true);
if(filtered[0])
return filtered[0]
if(collection.children)
return getFiltered(collection.children, obj, true);
}
angular.forEach(array, function(element){
var filtered = getFiltered(array, { id: element.id })
if(filtered[0]) filtered.name = element.name;
})

Related

Javascript Object Array loop not interrating

I have a problem with a for loop. What the problem is I have a for that is going through an object and it is only going through the array once. How can I loop through the entire object array loop? The current code is below:
var i = 0;
for (var key in data) {
console.log(data[key].allProducts[i]);
i++;
}
Well, since you are use the indexes here, why not at once use a for...of loop instead? Then you don't have any need accessing the properties itself
Depending on your data object, you might need to use Object.values( data ) or stick with your previous for...in loop, but I guess the principle is clear :)
const data = {
group1: {
allProducts: [
{ id: 1, name: 'product 1' },
{ id: 2, name: 'product 2' },
{ id: 3, name: 'product 3' },
{ id: 4, name: 'product 4' }
]
},
group2: {
allProducts: [
{ id: 5, name: 'product 5' }
]
}
};
for (let item of Object.values( data) ) {
for (let product of item.allProducts) {
console.log(product);
}
}
You only have one loop trying to control two variables, which isn't what you're trying to do. Assuming data keys are something like ['a', 'b', 'c'], you're actually getting data['a'][1], data['b'][2], data['c'][3].
What you need is two nested loops:
for (var key in data) {
var productsLength = data[key].allProducts.length;
for (var i = 0; i < productsLength; i++) {
console.log(data[key].allProducts[i]);
}
}

How can I return the property values of a nested JavaScript array nested in an object from JSON?

Given a JS array containing many objects which all contain arrays:
var data = [
{id: 1, name: "Fred", pages:[{url:"www.abc.com", title: "abc"}]},
{id: 2, name: "Wilma", pages:[{url:"www.123.com", title: "123"}]},
{id: 3, name: "Pebbles", pages:[{url:"www.xyz.com", title: "xyz"}]}
];
How do I efficiently extract the inner most array (pages) values into an array?
var dataArray = [
{url: "www.abc.com", title: "abc"},
{url: "www.123.com", title: "123"},
{url: "www.xyz.com", title: "xyz"}
]
The easiest way to do this is to use Array#map like so:
var dataArray = data.map(function(o){return o.pages});
If pages is an array of objects (not a single object), this will result in an array of arrays, which you will need to flatten out for example using Array#reduce:
dataArray = dataArray.reduce(function(a,b){return a.concat(b)}, []);
You are looking for a flatMap
var data = [
{id: 1, name: "Fred", pages:[{url:"www.abc.com", title: "abc"}]},
{id: 2, name: "Wilma", pages:[{url:"www.123.com", title: "123"}]},
{id: 3, name: "Pebbles", pages:[{url:"www.xyz.com", title: "xyz"}]}
];
const concat = (xs, ys) => xs.concat(ys);
const prop = x => y => y[x];
const flatMap = (f, xs) => xs.map(f).reduce(concat, []);
console.log(
flatMap(prop('pages'), data)
);
If by "efficiently" you actually mean "concisely", then
[].concat(...data.map(elt => elt.pages))
The data.map will result in an array of pages arrays. The [].concat(... then passes all the pages arrays as parameters to concat, which will combine all of their elements into a single array.
If you are programming in ES5, the equivalent would be
Array.prototype.concat.apply([], data.map(function(elt) { return elt.pages; }))
Here's a working example on how to achieve what you want:
var data = [
{id: 1, name: "Fred", pages:[{url:"www.abc.com", title: "abc"}, {url:"www.google.com", title: "Google"}]},
{id: 2, name: "Wilma", pages:[{url:"www.123.com", title: "123"}]},
{id: 3, name: "Pebbles", pages:[{url:"www.xyz.com", title: "xyz"}]}
];
var arr = Array();
var arr2 = Array();
// You can either iterate it like this:
for (var i = 0; i < data.length; i++) {
// If you only want the first page in your result, do:
// arr.push(data[i].pages[0]);
// If you want all pages in your result, you can iterate the pages too:
for (var a = 0; a < data[i].pages.length; a++) {
arr.push(data[i].pages[a]);
}
}
// Or use the array map method as suggested dtkaias
// (careful: will only work on arrays, not objects!)
//arr2 = data.map(function (o) { return o.pages[0]; });
// Or, for all pages in the array:
arr2 = [].concat(...data.map(function (o) { return o.pages; }));
console.log(arr);
console.log(arr2);
// Returns 2x [Object { url="www.abc.com", title="abc"}, Object { url="www.123.com", title="123"}, Object { url="www.xyz.com", title="xyz"}]
use array map() & reduce() method :
var data = [
{id: 1, name: "Fred", pages:[{url:"www.abc.com", title: "abc"}]},
{id: 2, name: "Wilma", pages:[{url:"www.123.com", title: "123"}]},
{id: 3, name: "Pebbles", pages:[{url:"www.xyz.com", title: "xyz"}]}
];
var dataArray = data.map(function(item) {
return item.pages;
});
dataArray = dataArray.reduce(function(a,b) {
return a.concat(b);
}, []);
console.log(dataArray);

Lodash object by Index

In lodash, how can I get an object from an array by the index at which it occurs, instead of searching for a key value.
var tv = [{id:1},{id:2}]
var data = //Desired result needs to be {id:2}
Let's take for example this collection:
var collection = [{id: 1, name: "Lorem"}, {id: 2, name: "Ipsum"}];
I will talk about two approaches, indexing and not indexing.
In general, indexing is better if you want to access many of the items, because you loop the collection once. If not, Meeseeks solution with find is the right choice.
Indexing
var byId = _.groupBy(collection, 'id');
var byName = _.groupBy(collection, 'name');
now you can reach each item by it indexed key:
console.log(byId[2]); // Object {id: 2, name: "Ipsum"}
console.log(byName.Lorem); // Object {id: 1, name: "Lorem"}
Without indexing
var item = _.find(collection, {id: 2});
console.log(item); // Object {id: 2, name: "Ipsum"}
I think what you're looking for is find
You can give it an object and it will return the matched element or undefined
Example
var arr = [ { id: 1, name: "Hello" }, { id: 2, name: "World" } ];
var data = _.find(arr, { id: 1 }); // => Object {id: 1, name: "Hello"}
var data = _.find(arr, { id: 3 }); // => undefined

is there a function in lodash to replace matched item

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);
}
};
In your case all you need to do is to find object in array and use Array.prototype.splice() method, read more details here:
var arr = [{id: 1, name: "Person 1"}, {id:2, name:"Person 2"}];
// Find item index using _.findIndex (thanks #AJ Richardson for comment)
var index = _.findIndex(arr, {id: 1});
// Replace item at index using native splice
arr.splice(index, 1, {id: 100, name: 'New object.'});
// "console.log" result
document.write(JSON.stringify( arr ));
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min.js"></script>
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.
[ES6] This code works for me.
let result = array.map(item => item.id === updatedItem.id ? updatedItem : item)
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
function first(arr, a, b) {
_.each(arr, function (x, idx) {
if (x.id === a.id) {
arr[idx] = b;
return false;
}
});
}
// solution with merge
function second(arr, a, b) {
const match = _.find(arr, a);
if (match) {
_.merge(match, b);
} else {
arr.push(b);
}
}
// most voted solution
function third(arr, a, b) {
const match = _.find(arr, a);
if (match) {
var index = _.indexOf(arr, _.find(arr, a));
arr.splice(index, 1, b);
} else {
arr.push(b);
}
}
// my approach
function fourth(arr, a, b){
let l;
for(l=0; l < arr.length && arr[l].id != a.id; l++) {}
l < arr.length ? arr[l] = b : arr.push(b);
}
function test(fn, times, el) {
const arr = [], size = 250;
for (let i = 0; i < size; i++) {
arr[i] = {id: i, name: `name_${i}`, test: "test"};
}
let start = Date.now();
_.times(times, () => {
const id = Math.round(Math.random() * size);
const a = {id};
const b = {id, name: `${id}_name`};
fn(arr, a, b);
});
el.innerHTML = Date.now() - start;
}
test(first, 1e5, document.getElementById("first"));
test(second, 1e5, document.getElementById("second"));
test(third, 1e5, document.getElementById("third"));
test(fourth, 1e5, document.getElementById("fourth"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>
<div>
<ol>
<li><b id="first"></b> ms [TC's first approach]</li>
<li><b id="second"></b> ms [solution with merge]</li>
<li><b id="third"></b> ms [most voted solution]</li>
<li><b id="fourth"></b> ms [my approach]</li>
</ol>
<div>
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');
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);
}
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 =>
predicate(element) ? replacement : element
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }
const result = arr.map(replace (predicate) (replacement))
console.log(result)
Recursive version - equivalent of mapping:
Requires destructuring and array spread.
const replace = predicate => replacement =>
{
const traverse = ([head, ...tail]) =>
head
? [predicate(head) ? replacement : head, ...tail]
: []
return traverse
}
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const predicate = element => element.id === 1
const replacement = { id: 100, name: 'New object.' }
const result = replace (predicate) (replacement) (arr)
console.log(result)
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 =>
({
...hashMap, //original HashMap
[key]: undefined, //delete the replaced value
[id]: values //assign replacement
})
// HashMap <-> array conversion
const toHashMapById = array =>
array.reduce(
(acc, { id, ...values }) =>
({ ...acc, [id]: values })
, {})
const toArrayById = hashMap =>
Object.entries(hashMap)
.filter( // filter out undefined values
([_, value]) => value
)
.map(
([id, values]) => ({ id, ...values })
)
const arr = [ { id: 1, name: "Person 1" }, { id:2, name:"Person 2" } ];
const replaceKey = 1
const replacement = { id: 100, name: 'New object.' }
// Create a HashMap from the array, treating id properties as keys
const hashMap = toHashMapById(arr)
console.log(hashMap)
// Result of replacement - notice an undefined value for replaced key
const resultHashMap = replace (replaceKey) (replacement) (hashMap)
console.log(resultHashMap)
// Final result of conversion from the HashMap to an array
const result = toArrayById (resultHashMap)
console.log(result)
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'}
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" }]
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)
If you want to make a function and keep it "lodash-ey", you can make a wrapper function that works with callbacks. It makes the function more general use.
To write this try something like
function findAllAndReplace(array, replacement, callback){
return array.map( element => callback(element) ? replacement : element )
}
To find and replace by key, just make your callback very simple. (itemInArray) => itemInArray.keyOnItem
But if you want more advanced functionality you can incorporate it with barely any extra effort. Here are some examples.
(Simple) Find the item with id 2, replace it to have an id: 7
const items = [{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}]
findAllAndReplace( items, {id: 7}, item => item.id === 2 )
(Slightly More Complex) Find 28 year old named John, and replace him with a 28 year old named Jon
const people = [
{
name: "John",
age: 20
},
{
name: "John",
age: 28
},
{
name: "Jim",
age: 28
},
]
findAllAndReplace(
people, // all the people
{ name: "Jon", age: 28 }, // Replacement value
(person) => person.name === "jon" && person.age === 21 // callback function
)
Also, the method above will find all instances that match and replace them, but if you just want to do it for one you could do something like below.
function findOneAndReplace(array, replacement, callback){
const splitIndex = array.findIndex(callback)
// This if statement can be ommitted, but might
// be handy depending on your use case
if(splitIndex < 0){
throw new Error("Swap Element not found")
}
const leadingTerms = array.slice(0, splitIndex)
const trailingTerms = array.slice(splitIndex + 1, array.length)
return [...leadingTerms, replacement, ...trailingTerms]
)
note: It might be useful to make your function break if it doesn't find a matching element, but if you don't want that feature you can cut those lines of code out.
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';
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"}]
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")
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);

Update if exists or add new element to array of objects - elegant way in javascript + lodash

So I have an array of objects like that:
var arr = [
{uid: 1, name: "bla", description: "cucu"},
{uid: 2, name: "smth else", description: "cucarecu"},
]
uid is unique id of the object in this array. I'm searching for the elegant way to modify the object if we have the object with the given uid, or add a new element, if the presented uid doesn't exist in the array. I imagine the function to be behave like that in js console:
> addOrReplace(arr, {uid: 1, name: 'changed name', description: "changed description"})
> arr
[
{uid: 1, name: "bla", description: "cucu"},
{uid: 2, name: "smth else", description: "cucarecu"},
]
> addOrReplace(arr, {uid: 3, name: 'new element name name', description: "cocoroco"})
> arr
[
{uid: 1, name: "bla", description: "cucu"},
{uid: 2, name: "smth else", description: "cucarecu"},
{uid: 3, name: 'new element name name', description: "cocoroco"}
]
My current way doesn't seem to be very elegant and functional:
function addOrReplace (arr, object) {
var index = _.findIndex(arr, {'uid' : object.uid});
if (-1 === index) {
arr.push(object);
} else {
arr[index] = object;
}
}
I'm using lodash, so I was thinking of something like modified _.union with custom equality check.
In your first approach, no need for Lodash thanks to findIndex():
function upsert(array, element) { // (1)
const i = array.findIndex(_element => _element.id === element.id);
if (i > -1) array[i] = element; // (2)
else array.push(element);
}
Example:
const array = [
{id: 0, name: 'Apple', description: 'fruit'},
{id: 1, name: 'Banana', description: 'fruit'},
{id: 2, name: 'Tomato', description: 'vegetable'}
];
upsert(array, {id: 2, name: 'Tomato', description: 'fruit'})
console.log(array);
/* =>
[
{id: 0, name: 'Apple', description: 'fruit'},
{id: 1, name: 'Banana', description: 'fruit'},
{id: 2, name: 'Tomato', description: 'fruit'}
]
*/
upsert(array, {id: 3, name: 'Cucumber', description: 'vegetable'})
console.log(array);
/* =>
[
{id: 0, name: 'Apple', description: 'fruit'},
{id: 1, name: 'Banana', description: 'fruit'},
{id: 2, name: 'Tomato', description: 'fruit'},
{id: 3, name: 'Cucumber', description: 'vegetable'}
]
*/
(1) other possible names: addOrReplace(), addOrUpdate(), appendOrUpdate(), insertOrUpdate()...
(2) can also be done with array.splice(i, 1, element)
Note that this approach is "mutable" (vs "immutable"): it means instead of returning a new array (without touching the original array), it modifies directly the original array.
You can use an object instead of an array:
var hash = {
'1': {uid: 1, name: "bla", description: "cucu"},
'2': {uid: 2, name: "smth else", description: "cucarecu"}
};
The keys are the uids. Now your function addOrReplace is simple like this:
function addOrReplace(hash, object) {
hash[object.uid] = object;
}
UPDATE
It's also possible to use an object as an index in addition to the array.
This way you've got fast lookups and also a working array:
var arr = [],
arrIndex = {};
addOrReplace({uid: 1, name: "bla", description: "cucu"});
addOrReplace({uid: 2, name: "smth else", description: "cucarecu"});
addOrReplace({uid: 1, name: "bli", description: "cici"});
function addOrReplace(object) {
var index = arrIndex[object.uid];
if(index === undefined) {
index = arr.length;
arrIndex[object.uid] = index;
}
arr[index] = object;
}
Take a look at the jsfiddle-demo (an object-oriented solution you'll find here)
If you do not mind about the order of the items in the end then more neat functional es6 approach would be as following:
function addOrReplace(arr, newObj){
return [...arr.filter((obj) => obj.uid !== newObj.uid), {...newObj}];
}
// or shorter one line version
const addOrReplace = (arr, newObj) => [...arr.filter((o) => o.uid !== newObj.uid), {...newObj}];
If item exist it will be excluded and then new item will be added at the end, basically it is replace, and if item is not found new object will be added at the end.
In this way you would have immutable list.
Only thing to know is that you would need to do some kind of sort in order to keep list order if you are for instance rendering list on the screen.
Hopefully, this will be handy to someone.
I personally do not like solutions that modify the original array/object, so this is what I did:
function addOrReplaceBy(arr = [], predicate, getItem) {
const index = _.findIndex(arr, predicate);
return index === -1
? [...arr, getItem()]
: [
...arr.slice(0, index),
getItem(arr[index]),
...arr.slice(index + 1)
];
}
And you would use it like:
var stuff = [
{ id: 1 },
{ id: 2 },
{ id: 3 },
{ id: 4 },
];
var foo = { id: 2, foo: "bar" };
stuff = addOrReplaceBy(
stuff,
{ id: foo.id },
(elem) => ({
...elem,
...foo
})
);
What I decided to do was to make it more flexible:
By using lodash -> _.findIndex(), the predicate can be multiple things
By passing a callback getItem(), you can decide whether to fully replace the item or do some modifications, as I did in my example.
Note: this solution contains some ES6 features such as destructuring, arrow functions, among others.
There is a second approach to this. We can use JavaScript Map object which "holds key-value pairs and remembers the original insertion order of the keys" plus "any value (both objects and primitive values) may be used as either a key or a value."
let myMap = new Map(
['1', { id: '1', first: true }] // key-value entry
['2', { id: '2', second: true }]
)
myMap = new Map([
...myMap,
['1', { id: '1', first: true, other: '...' }]
['3', { id: '3', third: true }]
])
myMap will have the following entries in order:
['1', { id: '1', first: true, other: '...' }]
['2', { id: '2', second: true }]
['3', { id: '3', third: true }]
We can use this characteristic of Maps to add or replace other elements:
function addOrReplaceBy(array, value, key = "id") {
return Array.from(
new Map([
...array.map(item => [ item[key], item ]),
[value[key], value]
]).values()
)
}
Maybe
_.mixin({
mergeById: function mergeById(arr, obj, idProp) {
var index = _.findIndex(arr, function (elem) {
// double check, since undefined === undefined
return typeof elem[idProp] !== "undefined" && elem[idProp] === obj[idProp];
});
if (index > -1) {
arr[index] = obj;
} else {
arr.push(obj);
}
return arr;
}
});
and
var elem = {uid: 3, name: 'new element name name', description: "cocoroco"};
_.mergeById(arr, elem, "uid");
Old post, but why not use the filter function?
// If you find the index of an existing uid, save its index then delete it
// --- after the filter add the new object.
function addOrReplace( argh, obj ) {
var index = -1;
argh.filter((el, pos) => {
if( el.uid == obj.uid )
delete argh[index = pos];
return true;
});
// put in place, or append to list
if( index == -1 )
argh.push(obj);
else
argh[index] = obj;
}
Here is a jsfiddle showing how it works.
What about having the indexes of the array same as the uid?, like:
arr = [];
arr[1] = {uid: 1, name: "bla", description: "cucu"};
arr[2] = {uid: 2, name: "smth else", description: "cucarecu"};
that way you could just simply use
arr[affectedId] = changedObject;
really complicated solutions :D
Here is a one liner:
const newArray = array.filter(obj => obj.id !== newObj.id).concat(newObj)
Backbone.Collection provides exactly this functionality. Save yourself the effort when you can!
var UidModel = Backbone.Model.extend({
idAttribute: 'uid'
});
var data = new Backbone.Collection([
{uid: 1, name: "bla", description: "cucu"},
{uid: 2, name: "smth else", description: "cucarecu"}
], {
model: UidModel
});
data.add({uid: 1, name: 'changed name', description: "changed description"}, {merge: true});
data.add({uid: 3, name: 'new element name name', description: "cocoroco"});
console.log(data.toJSON());

Categories