Union of array of objects in JavaScript - javascript

I'm trying to do union of different arrays:
const info1 = {id: 1}
const info2 = {id: 2}
const info3 = {id: 3}
const array1 = [info1, info2]
const array2 = [info1, info3]
const array3 = [info2, info3]
const union = [...new Set([...array1, ...array2, ...array3])]
console.log(union)
It's possible in my code that some or all array1, array2, array3 can be null, this is not shown in the code above.
However when one or more of them are null I get the error for example if array1 was assigned to null: array1 is not iterable.
How can I prevent getting this error?

You can use the nullish coalescing operator to fall back to an empty array if the 'array' is actually null.
const union = [
...new Set([...(array1 ?? []), ...(array2 ?? []), ...(array3 ?? [])]),
];

You can create a function that will perform the extra null check for you.
const info1 = {
id: 1
};
const info2 = {
id: 2
};
const info3 = {
id: 3
};
const array1 = [info1, info2];
const array2 = [info1, info3];
const array3 = [info2, info3];
const array4 = null;
// the spread operator here allow us to target every argument
// of the function
function union(...arrays) {
// the filter removes the *nullish* values
return Array.from(new Set(arrays.filter(x => !!x).flat()));
}
console.log(union(array1, array2, array3, array4))
/!\ Attention
One more thing, when you use a Set to remove the duplicates. It removes the duplicates because they are the same object.
If you create a new object, containing the same id, it will not be detected as the same value, because you compare reference and not the underlying values.
const info1 = {
id: 1
};
const info2 = {
id: 2
};
const info3 = {
id: 3
};
const array1 = [info1, info2];
const array2 = [info1, info3];
const array3 = [info2, {
id: 3
}];
const array4 = null;
function union(...arrays) {
return Array.from(new Set(arrays.filter(x => !!x).flat()));
}
console.log(union(array1, array2, array3, array4))

Modify your code as if an array gives you a null value then replace null with empty array to work properly as:
const info1 = {id: 1}
const info2 = {id: 2}
const info3 = {id: 3}
const array1 = [info1, info2]
const array2 = [info1, info3]
const array3 = null
const isNotNull=(array)=> array ? array : []
const union = [...new Set([...isNotNull(array1), ...isNotNull(array2), ...isNotNull(array3)])]
console.log(union)
function isNotNull checks an array if it equals null then it return an empty array

The most simple method is to use a Array#flatMap approach with a default array for virable with either an array or null.
unique = Array.from(new Set([array1, array2, array3].flatMap(v => v || [])));

Create a function that uses the rest operator to loop through each array;
Check if for null, otherwise, add to the set
const info1 = {id: 1}
const info2 = {id: 2}
const info3 = {id: 3}
const array1 = [info1, info2]
const array2 = [info1, info3]
const array3 = null;
function createSet(...all) {
// Tmp hold
let a = [];
// For each given array
for (var i = 0; i < all.length; i++) {
// If it's not null, add
if (all[i]) {
a.push(...all[i]);
}
}
// Create and return set
return [...new Set(a)];
}
console.log(createSet(array1, array2, array3));

Related

How to create multiple objects from array of entries in JavaScript?

I receive an array of entries from form using FormData(). It consists of information about the recipe. Like this:
const dataArr = [
['title', 'pizza'],
['image', 'url'],
['quantity-0', '1'],
['unit-0', 'kg'],
['description-0', 'Flour'],
['quantity-1', '2'],
['unit-1', 'tbsp'],
['description-1', 'Olive oil'],
... // more ingredients
];
which I need to reorganize in new object, like this:
const recipe = {
title: 'pizza',
image: 'url',
ingredients: [
{ quantity: '1', unit: 'kg', ingredient: 'Flour' },
{ quantity: '2', unit: 'tbsp', ingredient: 'Olive oil' },
...
],
};
So, for ingredients array I need to create multiple objects from received data. I came up with needed result, but it's not clean. I would appreciate your help coming up with universal function, when number of ingredients is unknown.
My solution: Form receives 6 ingredients max, therefore:
const ingredients = [];
// 1. Create an array with length of 6 (index helps to get ingredient-related data looping over the array)
const arrayOf6 = new Array(6).fill({});
arrayOf6.forEach((_, i) => {
// 2. In each iteration filter over all data to get an array for each ingredient
const ingArr = dataArr.filter(entry => {
return entry[0].startsWith(`unit-${i}`) ||
entry[0].startsWith(`quantity-${i}`) ||
entry[0].startsWith(`ingredient-${i}`);
});
// 3. Loop over each ingredient array and rename future properties
ingArr.forEach(entry => {
[key, value] = entry;
if(key.includes('ingredient')) entry[0] = 'description';
if(key.includes('quantity')) entry[0] = 'quantity';
if(key.includes('unit')) entry[0] = 'unit';
});
// 4. Transform array to object and push into ingredients array
const ingObj = Object.fromEntries(ingArr);
ingredients.push(ingObj);
});
// To finalize new object
const dataObj = Object.fromEntries(dataArr);
const recipe = {
title: dataObj.title,
image: dataObj.image,
ingredients,
};
You'll have to parse the values of the input array to extract the index. To build the result object, you could use reduce:
const dataArr = [['title', 'pizza'],['image', 'url'],['quantity-0', '1'],['unit-0', 'kg'],['description-0', 'Flour'],['quantity-1', '2'],['unit-1', 'tbsp'], ['description-1', 'Olive oil']];
const recipe = dataArr.reduce((recipe, [name, value]) => {
const [, prop, index] = name.match(/^(\w+)-(\d+)$/) ?? [];
if (prop) {
(recipe.ingredients[index] ??= {})[prop] = value;
} else {
recipe[name] = value;
}
return recipe;
}, { ingredients: [] });
console.log(recipe);
You don't need arrayOf6. You never use its elements for anything -- it seems like you're just using it as a replacement for a loop like for (let i = 0; i < 6; i++).
Just loop over dataArr and check whether the name has a number at the end. If it does, use that as an index into the ingredients array, otherwise use the name as the property of the ingredients object. Then you don't need to hard-code a limit to the number of ingredients.
const dataArr = [
['title', 'pizza'],
['image', 'url'],
['quantity-0', '1'],
['unit-0', 'kg'],
['description-0', 'Flour'],
['quantity-1', '2'],
['unit-1', 'tbsp'],
['description-1', 'Olive oil'],
// more ingredients
];
const recipe = {
ingredients: []
};
dataArr.forEach(([name, value]) => {
let match = name.match(/^(\w+)-(\d+)$/);
if (match) {
let type = match[1];
let index = match[2];
if (!recipe.ingredients[index]) {
recipe.ingredients[index] = {};
}
recipe.ingredients[index][type] = value;
} else {
recipe[name] = value;
}
});
console.log(recipe);
Separating the key-parsing logic helps me think about the concerns more clearly:
const orderedKeyRegExp = /^(.+)-(\d+)$/;
function parseKey (key) {
const match = key.match(orderedKeyRegExp);
// Return -1 for the index if the key pattern isn't part of a numbered sequence
if (!match) return {index: -1, name: key};
return {
index: Number(match[2]),
name: match[1],
};
}
function transformRecipeEntries (entries) {
const result = {};
const ingredients = [];
for (const [key, value] of entries) {
const {index, name} = parseKey(key);
if (index >= 0) (ingredients[index] ??= {})[name] = value;
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// Assign an empty object to the element of the array at the index
// (if it doesn't already exist)
else result[name] = value;
}
if (ingredients.length > 0) result.ingredients = ingredients;
return result;
}
const entries = [
['title', 'pizza'],
['image', 'url'],
['quantity-0', '1'],
['unit-0', 'kg'],
['description-0', 'Flour'],
['quantity-1', '2'],
['unit-1', 'tbsp'],
['description-1', 'Olive oil'],
// ...more ingredients
];
const result = transformRecipeEntries(entries);
console.log(result);

pushing data inside an existing empty array

Basically I have an main object dataToMovie, I have empty arrays. I want to display the content of the arr variable into mov[] and the content of the arr2 into ser[]. I have attempted to do something as you can see. I am looking to do this seperately for each array as I will have multiple data in the future
const dataToMovie = {
createMovie,
links: {
mov: [],
ser: [],
rev: [],
ext: [],
},
};
const dataToInsert1 = {
'model-10389': 164703,
'model-10388': 164704,
'model-10387': 164705,
};
const dataToInsert2 = {
'model-10389': [1656, 1234, 1245],
'model-10384': [1656, 1234, 1245],
'model-10383': [1656, 1234, 1245],
};
const arr = Object.entries(dataToInsert1).map((entry) => ({
id: entry[0].substring(6, entry[0].length),
value: entry[1],
}));
//dataToMovie.links.mov[arr]
const arr2 = Object.entries(dataToInsert2).map(([key, value]) => ({
modelId: key.substring(6),
ids: value,
}));
//dataToMovie.links.ser[arr2]
concat is a good candidate for this operation. it returns a merged array.
dataToMovie.links.mov = dataToMovie.links.mov.concat( arr )
dataToMovie.links.ser = dataToMovie.links.set.concat( arr2 )
You are close. if the arrays are empty you can just do:
dataToMovie.links.mov = arr
dataToMovie.links.ser= arr2
If the arrays have items in them and you just want to add to them, you can use the spread operator
dataToMovie.links.mov = [...dataToMovie.links.mov, ...arr]
dataToMovie.links.ser = [...dataToMovie.links.ser, ...arr2]

Can I add or edit object in array by field [duplicate]

I have this javascript object:
var arr1 = [{id:'124',name:'qqq'},
{id:'589',name:'www'},
{id:'45',name:'eee'},
{id:'567',name:'rrr'}]
var arr2 = [{id:'124',name:'ttt'},
{id:'45',name:'yyy'}]
I need to replace objects in arr1 with items from arr2 with same id.
So here is the result I want to get:
var arr1 = [{id:'124',name:'ttt'},
{id:'589',name:'www'},
{id:'45',name:'yyy'},
{id:'567',name:'rrr'}]
How can I implement it using javascript?
You can use Array#map with Array#find.
arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
var arr1 = [{
id: '124',
name: 'qqq'
}, {
id: '589',
name: 'www'
}, {
id: '45',
name: 'eee'
}, {
id: '567',
name: 'rrr'
}];
var arr2 = [{
id: '124',
name: 'ttt'
}, {
id: '45',
name: 'yyy'
}];
var res = arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
console.log(res);
Here, arr2.find(o => o.id === obj.id) will return the element i.e. object from arr2 if the id is found in the arr2. If not, then the same element in arr1 i.e. obj is returned.
What's wrong with Object.assign(target, source) ?
Arrays are still type object in Javascript, so using assign should still reassign any matching keys parsed by the operator as long as matching keys are found, right?
There is always going to be a good debate on time vs space, however these days I've found using space is better for the long run.. Mathematics aside let look at a one practical approach to the problem using hashmaps, dictionaries, or associative array's whatever you feel like labeling the simple data structure..
var marr2 = new Map(arr2.map(e => [e.id, e]));
arr1.map(obj => marr2.has(obj.id) ? marr2.get(obj.id) : obj);
I like this approach because though you could argue with an array with low numbers you are wasting space because an inline approach like #Tushar approach performs indistinguishably close to this method. However I ran some tests and the graph shows how performant in ms both methods perform from n 0 - 1000. You can decide which method works best for you, for your situation but in my experience users don't care to much about small space but they do care about small speed.
Here is my performance test I ran for source of data
var n = 1000;
var graph = new Array();
for( var x = 0; x < n; x++){
var arr1s = [...Array(x).keys()];
var arr2s = arr1s.filter( e => Math.random() > .5);
var arr1 = arr1s.map(e => {return {id: e, name: 'bill'}});
var arr2 = arr2s.map(e => {return {id: e, name: 'larry'}});
// Map 1
performance.mark('p1s');
var marr2 = new Map(arr2.map(e => [e.id, e]));
arr1.map(obj => marr2.has(obj.id) ? marr2.get(obj.id) : obj);
performance.mark('p1e');
// Map 2
performance.mark('p2s');
arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
performance.mark('p2e');
graph.push({ x: x, r1: performance.measure('HashMap Method', 'p1s', 'p1e').duration, r2: performance.measure('Inner Find', 'p2s','p2e').duration});
}
Since you're using Lodash you could use _.map and _.find to make sure major browsers are supported.
In the end I would go with something like:
function mergeById(arr) {
return {
with: function(arr2) {
return _.map(arr, item => {
return _.find(arr2, obj => obj.id === item.id) || item
})
}
}
}
var result = mergeById([{id:'124',name:'qqq'},
{id:'589',name:'www'},
{id:'45',name:'eee'},
{id:'567',name:'rrr'}])
.with([{id:'124',name:'ttt'}, {id:'45',name:'yyy'}])
console.log(result);
<script src="https://raw.githubusercontent.com/lodash/lodash/4.13.1/dist/lodash.js"></script>
Thanks to ES6 we can made it with easy way -> for example on util.js module ;))).
Merge 2 array of entity
export const mergeArrays = (arr1, arr2) =>
arr1 && arr1.map(obj => arr2 && arr2.find(p => p.id === obj.id) || obj);
gets 2 array and merges it.. Arr1 is main array which is priority is
high on merge process
Merge array with same type of entity
export const mergeArrayWithObject = (arr, obj) => arr && arr.map(t => t.id === obj.id ? obj : t);
it merges the same kind of array of type with some kind of type for
example: array of person ->
[{id:1, name:"Bir"},{id:2, name: "Iki"},{id:3, name:"Uc"}]
second param Person {id:3, name: "Name changed"}
result is
[{id:1, name:"Bir"},{id:2, name: "Iki"},{id:3, name:"Name changed"}]
I like to go through arr2 with foreach() and use findIndex() for checking for occurrence in arr1:
var arr1 = [{id:'124',name:'qqq'},
{id:'589',name:'www'},
{id:'45',name:'eee'},
{id:'567',name:'rrr'}]
var arr2 = [{id:'124',name:'ttt'},
{id:'45',name:'yyy'}]
arr2.forEach(element => {
const itemIndex = arr1.findIndex(o => o.id === element.id);
if(itemIndex > -1) {
arr1[itemIndex] = element;
} else {
arr1 = arr1.push(element);
}
});
console.log(arr1)
I'd like to suggest another solution:
const objectToReplace = this.array.find(arrayItem => arrayItem.id === requiredItem.id);
Object.assign(objectToReplace, newObject);
Considering that the accepted answer is probably inefficient for large arrays, O(nm), I usually prefer this approach, O(2n + 2m):
function mergeArrays(arr1 = [], arr2 = []){
//Creates an object map of id to object in arr1
const arr1Map = arr1.reduce((acc, o) => {
acc[o.id] = o;
return acc;
}, {});
//Updates the object with corresponding id in arr1Map from arr2,
//creates a new object if none exists (upsert)
arr2.forEach(o => {
arr1Map[o.id] = o;
});
//Return the merged values in arr1Map as an array
return Object.values(arr1Map);
}
Unit test:
it('Merges two arrays using id as the key', () => {
var arr1 = [{id:'124',name:'qqq'}, {id:'589',name:'www'}, {id:'45',name:'eee'}, {id:'567',name:'rrr'}];
var arr2 = [{id:'124',name:'ttt'}, {id:'45',name:'yyy'}];
const actual = mergeArrays(arr1, arr2);
const expected = [{id:'124',name:'ttt'}, {id:'589',name:'www'}, {id:'45',name:'yyy'}, {id:'567',name:'rrr'}];
expect(actual.sort((a, b) => (a.id < b.id)? -1: 1)).toEqual(expected.sort((a, b) => (a.id < b.id)? -1: 1));
})
// here find all the items that are not it the arr1
const temp = arr1.filter(obj1 => !arr2.some(obj2 => obj1.id === obj2.id))
// then just concat it
arr1 = [...temp, ...arr2]
Here a more transparent approach. I find the oneliners harder to read and harder to debug.
export class List {
static replace = (object, list) => {
let newList = [];
list.forEach(function (item) {
if (item.id === object.id) {
newList.push(object);
} else {
newList.push(item);
}
});
return newList;
}
}
If you don't care about the order of the array, then you may want to get the difference between arr1 and arr2 by id using differenceBy() and then simply use concat() to append all the updated objects.
var result = _(arr1).differenceBy(arr2, 'id').concat(arr2).value();
var arr1 = [{
id: '124',
name: 'qqq'
}, {
id: '589',
name: 'www'
}, {
id: '45',
name: 'eee'
}, {
id: '567',
name: 'rrr'
}]
var arr2 = [{
id: '124',
name: 'ttt'
}, {
id: '45',
name: 'yyy'
}];
var result = _(arr1).differenceBy(arr2, 'id').concat(arr2).value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
I am only submitting this answer because people expressed concerns over browsers and maintaining the order of objects. I recognize that it is not the most efficient way to accomplish the goal.
Having said this, I broke the problem down into two functions for readability.
// The following function is used for each itertion in the function updateObjectsInArr
const newObjInInitialArr = function(initialArr, newObject) {
let id = newObject.id;
let newArr = [];
for (let i = 0; i < initialArr.length; i++) {
if (id === initialArr[i].id) {
newArr.push(newObject);
} else {
newArr.push(initialArr[i]);
}
}
return newArr;
};
const updateObjectsInArr = function(initialArr, newArr) {
let finalUpdatedArr = initialArr;
for (let i = 0; i < newArr.length; i++) {
finalUpdatedArr = newObjInInitialArr(finalUpdatedArr, newArr[i]);
}
return finalUpdatedArr
}
const revisedArr = updateObjectsInArr(arr1, arr2);
jsfiddle
function getMatch(elem) {
function action(ele, val) {
if(ele === val){
elem = arr2[i];
}
}
for (var i = 0; i < arr2.length; i++) {
action(elem.id, Object.values(arr2[i])[0]);
}
return elem;
}
var modified = arr1.map(getMatch);
I went with this, because it makes sense to me. Comments added for readers!
masterData = [{id: 1, name: "aaaaaaaaaaa"},
{id: 2, name: "Bill"},
{id: 3, name: "ccccccccc"}];
updatedData = [{id: 3, name: "Cat"},
{id: 1, name: "Apple"}];
updatedData.forEach(updatedObj=> {
// For every updatedData object (dataObj), find the array index in masterData where the IDs match.
let indexInMasterData = masterData.map(masterDataObj => masterDataObj.id).indexOf(updatedObj.id); // First make an array of IDs, to use indexOf().
// If there is a matching ID (and thus an index), replace the existing object in masterData with the updatedData's object.
if (indexInMasterData !== undefined) masterData.splice(indexInMasterData, 1, updatedObj);
});
/* masterData becomes [{id: 1, name: "Apple"},
{id: 2, name: "Bill"},
{id: 3, name: "Cat"}]; as you want.`*/
The accepted answer using array.map is correct but you have to remember to assign it to another variable since array.map doesnt change original array, it actually creates a new array.
//newArr contains the mapped array from arr2 to arr1.
//arr1 still contains original value
var newArr = arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
Array.prototype.update = function(...args) {
return this.map(x=>args.find((c)=>{return c.id===x.id}) || x)
}
const result =
[
{id:'1',name:'test1'},
{id:'2',name:'test2'},
{id:'3',name:'test3'},
{id:'4',name:'test4'}
]
.update({id:'1',name:'test1.1'}, {id:'3',name:'test3.3'})
console.log(result)
This is how I do it in TypeScript:
const index = this.array.indexOf(this.objectToReplace);
this.array[index] = newObject;

How to generate this JSON object?

I have an array with the following values:
['persona1', 'Persona2', 'Persona3', 'Persona4']
And I have another array with the names of each person:
['JUAN', 'CARLOS', 'PEDRO','MATEO']
I need to generate a JSON object like the following:
{ persona1: 'JUAN', persona2: 'CARLOS', persona3: 'PEDRO', persona4: 'MATEO' }
Each value in the first array becomes the key for the corresponding value in the second array.
How can I do this in Javascript?
Loop over the array and generate the object dynamically.
let arr1 = [ 'foo', 'bar' ]
let arr2 = [ 'baz', 'qux' ]
let obj = {}
for (let i = 0; i < arr1.length; i++) obj[arr1[i]] = arr2[i];
console.log(obj);
You could use reduce here
const arr1 = ["persona1", "Persona2", "Persona3", "Persona4"];
const arr2 = ["JUAN", "CARLOS", "PEDRO", "MATEO"];
const result = arr1.reduce((acc, curr, i) => {
acc[curr] = arr2[i];
return acc;
}, {});
console.log(result);
You can create an empty object, loop over the first array, and take each item in the array as the key, and take each item of the second array as the value.
Then you can you the JSON.stringify() function to convert that object to JSON string.
const arr1 = ['persona1', 'Persona2', 'Persona3', 'Persona4']
const arr2 = ['JUAN', 'CARLOS', 'PEDRO', 'MATEO']
const output = {}
arr1.forEach((item, i) => output[item] = arr2[i])
console.log(JSON.stringify(output))
const personKeyArr = ['persona1', 'Persona2', 'Persona3', 'Persona4']
const personValArr = ['JUAN', 'CARLOS', 'PEDRO','MATEO']
const retValue = {}
personKeyArr.forEach((x,i)=>retValue[x] = [personValArr[i]])
What you want to do is a combination of zipping two arrays together and then converting the resulting pairs into the key/value entries in an object. As #Phil mentioned in their comment, the lodash library has a function to do this called zipObject, but if you don't want to load that entire library, it's not hard to create your own with reduce. Here's one version (found at https://lowrey.me/lodash-zipobject-in-es6-javascript/):
const zipObject = (props, values) => {
return props.reduce((prev, prop, i) => {
return Object.assign(prev, { [prop]: values[i] });
}, {});
};
Running it on your data:
keys = ['persona1', 'Persona2', 'Persona3', 'Persona4']
nombres = ['JUAN', 'CARLOS', 'PEDRO','MATEO']
zipObject(keys, nombres)
//=>
{
persona1: 'JUAN',
Persona2: 'CARLOS',
Persona3: 'PEDRO',
Persona4: 'MATEO'
}

Replacing objects in array

I have this javascript object:
var arr1 = [{id:'124',name:'qqq'},
{id:'589',name:'www'},
{id:'45',name:'eee'},
{id:'567',name:'rrr'}]
var arr2 = [{id:'124',name:'ttt'},
{id:'45',name:'yyy'}]
I need to replace objects in arr1 with items from arr2 with same id.
So here is the result I want to get:
var arr1 = [{id:'124',name:'ttt'},
{id:'589',name:'www'},
{id:'45',name:'yyy'},
{id:'567',name:'rrr'}]
How can I implement it using javascript?
You can use Array#map with Array#find.
arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
var arr1 = [{
id: '124',
name: 'qqq'
}, {
id: '589',
name: 'www'
}, {
id: '45',
name: 'eee'
}, {
id: '567',
name: 'rrr'
}];
var arr2 = [{
id: '124',
name: 'ttt'
}, {
id: '45',
name: 'yyy'
}];
var res = arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
console.log(res);
Here, arr2.find(o => o.id === obj.id) will return the element i.e. object from arr2 if the id is found in the arr2. If not, then the same element in arr1 i.e. obj is returned.
What's wrong with Object.assign(target, source) ?
Arrays are still type object in Javascript, so using assign should still reassign any matching keys parsed by the operator as long as matching keys are found, right?
There is always going to be a good debate on time vs space, however these days I've found using space is better for the long run.. Mathematics aside let look at a one practical approach to the problem using hashmaps, dictionaries, or associative array's whatever you feel like labeling the simple data structure..
var marr2 = new Map(arr2.map(e => [e.id, e]));
arr1.map(obj => marr2.has(obj.id) ? marr2.get(obj.id) : obj);
I like this approach because though you could argue with an array with low numbers you are wasting space because an inline approach like #Tushar approach performs indistinguishably close to this method. However I ran some tests and the graph shows how performant in ms both methods perform from n 0 - 1000. You can decide which method works best for you, for your situation but in my experience users don't care to much about small space but they do care about small speed.
Here is my performance test I ran for source of data
var n = 1000;
var graph = new Array();
for( var x = 0; x < n; x++){
var arr1s = [...Array(x).keys()];
var arr2s = arr1s.filter( e => Math.random() > .5);
var arr1 = arr1s.map(e => {return {id: e, name: 'bill'}});
var arr2 = arr2s.map(e => {return {id: e, name: 'larry'}});
// Map 1
performance.mark('p1s');
var marr2 = new Map(arr2.map(e => [e.id, e]));
arr1.map(obj => marr2.has(obj.id) ? marr2.get(obj.id) : obj);
performance.mark('p1e');
// Map 2
performance.mark('p2s');
arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
performance.mark('p2e');
graph.push({ x: x, r1: performance.measure('HashMap Method', 'p1s', 'p1e').duration, r2: performance.measure('Inner Find', 'p2s','p2e').duration});
}
Since you're using Lodash you could use _.map and _.find to make sure major browsers are supported.
In the end I would go with something like:
function mergeById(arr) {
return {
with: function(arr2) {
return _.map(arr, item => {
return _.find(arr2, obj => obj.id === item.id) || item
})
}
}
}
var result = mergeById([{id:'124',name:'qqq'},
{id:'589',name:'www'},
{id:'45',name:'eee'},
{id:'567',name:'rrr'}])
.with([{id:'124',name:'ttt'}, {id:'45',name:'yyy'}])
console.log(result);
<script src="https://raw.githubusercontent.com/lodash/lodash/4.13.1/dist/lodash.js"></script>
I'd like to suggest another solution:
const objectToReplace = this.array.find(arrayItem => arrayItem.id === requiredItem.id);
Object.assign(objectToReplace, newObject);
Thanks to ES6 we can made it with easy way -> for example on util.js module ;))).
Merge 2 array of entity
export const mergeArrays = (arr1, arr2) =>
arr1 && arr1.map(obj => arr2 && arr2.find(p => p.id === obj.id) || obj);
gets 2 array and merges it.. Arr1 is main array which is priority is
high on merge process
Merge array with same type of entity
export const mergeArrayWithObject = (arr, obj) => arr && arr.map(t => t.id === obj.id ? obj : t);
it merges the same kind of array of type with some kind of type for
example: array of person ->
[{id:1, name:"Bir"},{id:2, name: "Iki"},{id:3, name:"Uc"}]
second param Person {id:3, name: "Name changed"}
result is
[{id:1, name:"Bir"},{id:2, name: "Iki"},{id:3, name:"Name changed"}]
I like to go through arr2 with foreach() and use findIndex() for checking for occurrence in arr1:
var arr1 = [{id:'124',name:'qqq'},
{id:'589',name:'www'},
{id:'45',name:'eee'},
{id:'567',name:'rrr'}]
var arr2 = [{id:'124',name:'ttt'},
{id:'45',name:'yyy'}]
arr2.forEach(element => {
const itemIndex = arr1.findIndex(o => o.id === element.id);
if(itemIndex > -1) {
arr1[itemIndex] = element;
} else {
arr1 = arr1.push(element);
}
});
console.log(arr1)
Considering that the accepted answer is probably inefficient for large arrays, O(nm), I usually prefer this approach, O(2n + 2m):
function mergeArrays(arr1 = [], arr2 = []){
//Creates an object map of id to object in arr1
const arr1Map = arr1.reduce((acc, o) => {
acc[o.id] = o;
return acc;
}, {});
//Updates the object with corresponding id in arr1Map from arr2,
//creates a new object if none exists (upsert)
arr2.forEach(o => {
arr1Map[o.id] = o;
});
//Return the merged values in arr1Map as an array
return Object.values(arr1Map);
}
Unit test:
it('Merges two arrays using id as the key', () => {
var arr1 = [{id:'124',name:'qqq'}, {id:'589',name:'www'}, {id:'45',name:'eee'}, {id:'567',name:'rrr'}];
var arr2 = [{id:'124',name:'ttt'}, {id:'45',name:'yyy'}];
const actual = mergeArrays(arr1, arr2);
const expected = [{id:'124',name:'ttt'}, {id:'589',name:'www'}, {id:'45',name:'yyy'}, {id:'567',name:'rrr'}];
expect(actual.sort((a, b) => (a.id < b.id)? -1: 1)).toEqual(expected.sort((a, b) => (a.id < b.id)? -1: 1));
})
// here find all the items that are not it the arr1
const temp = arr1.filter(obj1 => !arr2.some(obj2 => obj1.id === obj2.id))
// then just concat it
arr1 = [...temp, ...arr2]
Here a more transparent approach. I find the oneliners harder to read and harder to debug.
export class List {
static replace = (object, list) => {
let newList = [];
list.forEach(function (item) {
if (item.id === object.id) {
newList.push(object);
} else {
newList.push(item);
}
});
return newList;
}
}
If you don't care about the order of the array, then you may want to get the difference between arr1 and arr2 by id using differenceBy() and then simply use concat() to append all the updated objects.
var result = _(arr1).differenceBy(arr2, 'id').concat(arr2).value();
var arr1 = [{
id: '124',
name: 'qqq'
}, {
id: '589',
name: 'www'
}, {
id: '45',
name: 'eee'
}, {
id: '567',
name: 'rrr'
}]
var arr2 = [{
id: '124',
name: 'ttt'
}, {
id: '45',
name: 'yyy'
}];
var result = _(arr1).differenceBy(arr2, 'id').concat(arr2).value();
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.js"></script>
I am only submitting this answer because people expressed concerns over browsers and maintaining the order of objects. I recognize that it is not the most efficient way to accomplish the goal.
Having said this, I broke the problem down into two functions for readability.
// The following function is used for each itertion in the function updateObjectsInArr
const newObjInInitialArr = function(initialArr, newObject) {
let id = newObject.id;
let newArr = [];
for (let i = 0; i < initialArr.length; i++) {
if (id === initialArr[i].id) {
newArr.push(newObject);
} else {
newArr.push(initialArr[i]);
}
}
return newArr;
};
const updateObjectsInArr = function(initialArr, newArr) {
let finalUpdatedArr = initialArr;
for (let i = 0; i < newArr.length; i++) {
finalUpdatedArr = newObjInInitialArr(finalUpdatedArr, newArr[i]);
}
return finalUpdatedArr
}
const revisedArr = updateObjectsInArr(arr1, arr2);
jsfiddle
function getMatch(elem) {
function action(ele, val) {
if(ele === val){
elem = arr2[i];
}
}
for (var i = 0; i < arr2.length; i++) {
action(elem.id, Object.values(arr2[i])[0]);
}
return elem;
}
var modified = arr1.map(getMatch);
I went with this, because it makes sense to me. Comments added for readers!
masterData = [{id: 1, name: "aaaaaaaaaaa"},
{id: 2, name: "Bill"},
{id: 3, name: "ccccccccc"}];
updatedData = [{id: 3, name: "Cat"},
{id: 1, name: "Apple"}];
updatedData.forEach(updatedObj=> {
// For every updatedData object (dataObj), find the array index in masterData where the IDs match.
let indexInMasterData = masterData.map(masterDataObj => masterDataObj.id).indexOf(updatedObj.id); // First make an array of IDs, to use indexOf().
// If there is a matching ID (and thus an index), replace the existing object in masterData with the updatedData's object.
if (indexInMasterData !== undefined) masterData.splice(indexInMasterData, 1, updatedObj);
});
/* masterData becomes [{id: 1, name: "Apple"},
{id: 2, name: "Bill"},
{id: 3, name: "Cat"}]; as you want.`*/
The accepted answer using array.map is correct but you have to remember to assign it to another variable since array.map doesnt change original array, it actually creates a new array.
//newArr contains the mapped array from arr2 to arr1.
//arr1 still contains original value
var newArr = arr1.map(obj => arr2.find(o => o.id === obj.id) || obj);
Array.prototype.update = function(...args) {
return this.map(x=>args.find((c)=>{return c.id===x.id}) || x)
}
const result =
[
{id:'1',name:'test1'},
{id:'2',name:'test2'},
{id:'3',name:'test3'},
{id:'4',name:'test4'}
]
.update({id:'1',name:'test1.1'}, {id:'3',name:'test3.3'})
console.log(result)
This is how I do it in TypeScript:
const index = this.array.indexOf(this.objectToReplace);
this.array[index] = newObject;

Categories

Resources