Related
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());
Assuming that I have 2 multidimensional arrays of objects
const products = [
{
id: 1
name: 'lorem'
},
{
id: 3,
name: 'ipsum'
}
];
const tmp_products = [
{
id: 1
name: 'lorem'
},
{
id: 14,
name: 'porros'
},
{
id: 3,
name: 'ipsum'
},
{
id: 105,
name: 'dolor'
},
{
id: 32,
name: 'simet'
}
];
What is the correct way to find the missing indexes by id property?
I'm expecting an output such as [1,3,4] since those objects are not present in products
I found a similar question but applied to plain arrays:
Javascript find index of missing elements of two arrays
var a = ['a', 'b', 'c'],
b = ['b'],
result = [];
_.difference(a, b).forEach(function(t) {result.push(a.indexOf(t))});
console.log(result);
I'd like to use ES6 or lodash to get this as short as possible
You can use sets to do it quickly:
const productIds = new Set(products.map(v => v.id));
const inds = tmp_products
.map((v, i) => [v, i])
.filter(([v, i]) => !productIds.has(v.id))
.map(([v, i]) => i);
inds // [1, 3, 4]
You can use Array.prototype.reduce function to get the list of missing products' index.
Inside reduce callback, you can check if the product is included in products array or not using Array.prototype.some and based on that result, you can decide to add the product index or not.
const products = [
{
id: 1,
name: 'lorem'
},
{
id: 3,
name: 'ipsum'
}
];
const tmp_products = [
{
id: 1,
name: 'lorem'
},
{
id: 14,
name: 'porros'
},
{
id: 3,
name: 'ipsum'
},
{
id: 105,
name: 'dolor'
},
{
id: 32,
name: 'simet'
}
];
const missingIndex = tmp_products.reduce((acc, curV, curI) => {
if (!products.some((item) => item.id === curV.id && item.name === curV.name)) {
acc.push(curI);
}
return acc;
}, []);
console.log(missingIndex);
With lodash you could use differenceWith:
_(tmp_products)
.differenceWith(products, _.isEqual)
.map(prod => tmp_products.indexOf(prod))
.value()
This may not be great for performance, but it depends on how many items you have. With the size of your arrays this should perform ok.
I have a two arrays, and I want to match their ID values and then get the index of that id in the second array. I know this sounds simple but I'm super new to the syntax, and I'm having a hard time picturing what this should look like. can anyone model that in simple terms?
example functionality:
var array1 = { id:2, name: 'preston'}
array2 = {
{
id: 1
name: 'john'
},
{
id: 2
name: 'bob'
}
Expected behavior
where both ids = 2, give index of array2.
returns 1
can anyone show me?
You can use findIndex on array2
Try this:
var array1 = {
id: 2,
name: 'preston'
}
var array2 = [{
id: 1,
name: 'john'
},
{
id: 2,
name: 'bob'
}
]
console.log(array2.findIndex(item => item.id === array1.id))
Or use indexOf with map if you want support for IE as well without polyfills.
var array1 = {
id: 2,
name: 'preston'
}
var array2 = [{
id: 1,
name: 'john'
},
{
id: 2,
name: 'bob'
}
]
console.log(array2.map(item => item.id).indexOf(array1.id))
Iterate over each item in array1 using forEach(). Find each item's index in array2 using findIndex().
var array1 = [{id:2, name: "preston"}];
var array2 = [{id: 1, name: "john" }, {id: 2, name: "bob" }];
array1.forEach(item => {
let index = array2.findIndex(obj => obj.id === item.id);
console.log(index);
});
I have 2 arrays of objects, they each have an id in common. I need a property from objects of array 2 added to objects array 1, if they have matching ids.
Array 1:
[
{
id: 1,
name: tom,
age: 24
},
{
id: 2,
name: tim,
age: 25
},
{
id: 3,
name: jack,
age: 24
},
]
Array 2:
[
{
id: 1,
gender: male,
eyeColour: blue,
weight: 150
},
{
id: 2,
gender: male,
eyeColour: green,
weight: 175
},
{
id: 3,
gender: male,
eyeColour: hazel,
weight: 200
},
]
Desired Outcome:
[
{
id: 1,
name: tom,
age: 24,
eyeColour: blue,
},
{
id: 2,
name: tim,
age: 25,
eyeColour: green,
},
{
id: 3,
name: jack,
age: 24,
eyeColour: hazel,
},
]
I tried using lodash _.merge function but then I end up with all properties into one array, when I only want eyeColour added.
Lodash remains a highly useful bag of utilities, but with the advent of ES6 some of its use cases are less compelling.
For each object (person) in the first array, find the object in the second array with matching ID (see function findPerson). Then merge the two.
function update(array1, array2) {
var findPerson = id => array2.find(person => person.id === id);
array1.forEach(person => Object.assign(person, findPerson(person.id));
}
For non-ES6 environments, rewrite arrow functions using traditional syntax. If Array#find is not available, write your own or use some equivalent. For Object.assign, if you prefer use your own equivalent such as _.extend.
This will merge all properties from array2 into array1. To only merge eyeColour:
function update(array1, array2) {
var findPerson = id => array2.find(person => person.id === id);
array1.forEach(person => {
var person2 = findPerson(person.id));
var {eyeColour} = person2;
Object.assign(person, {eyeColour});
});
}
Just noticed Paul answered while I was working on my answer but I'll add my very similar code anyway:
var getEyeColour = function (el) { return _.pick(el, 'eyeColour'); }
var out = _.merge(arr1, _.map(arr2, getEyeColour));
DEMO
You can use pick to get only the properties you want before merging:
var result = _.merge( arr1, _.map( arr2, function( obj ) {
return _.pick( obj, 'id', 'eyeColour' );
}));
A solution in plain Javascript
This is a more generic solution for merging two arrays which have different properties to union in one object with a common key and some properties to add.
var array1 = [{ id: 1, name: 'tom', age: 24 }, { id: 2, name: 'tim', age: 25 }, { id: 3, name: 'jack', age: 24 }, ],
array2 = [{ id: 1, gender: 'male', eyeColour: 'blue', weight: 150 }, { id: 2, gender: 'male', eyeColour: 'green', weight: 175 }, { id: 3, gender: 'male', eyeColour: 'hazel', weight: 200 }, ];
function merge(a, b, id, keys) {
var array = [], object = {};
function m(c) {
if (!object[c[id]]) {
object[c[id]] = {};
object[c[id]][id] = c[id];
array.push(object[c[id]]);
}
keys.forEach(function (k) {
if (k in c) {
object[c[id]][k] = c[k];
}
});
}
a.forEach(m);
b.forEach(m);
return array;
}
document.write('<pre>' + JSON.stringify(merge(array1, array2, 'id', ['name', 'age', 'eyeColour']), 0, 4) + '</pre>');
I was looking for the same, but I want to match Id before merging
And in my case, second array may have different number of items, finally I came with this:
var out = arr1.map(x => {
return { ...x, eyeColour: arr2.find(y => x.id === y.id)?.eyeColour }
});
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());