Comparing two Objects by id and creating the new one - javascript

So I have two objects with this structure:
const obj1 = { data:
[ {
id: 1,
name: 'Linda'
},
{
id: 2,
name: 'Mark'
}
];
const obj2 = [
{
id: 1,
salary: "2000, 60 USD"
},
undefined
],
[
{
id: 2,
salary: "4000, 50 USD"
},
undefined
]
I need to make a function to combine both of these into one object, based on id.
So the final results would be:
const finalObj = { data:
[ {
id: 1,
name: 'Linda',
salary: "2000, 60 USD"
},
{
id: 2,
name: 'Mark',
salary: "4000, 50 USD"
}
];
I have checked other questions, but could not find anything that would help. It can be done with lodash afaik, but don't know how.
I have tried the following:
finalObj = obj1.data.map(x => {
return {
...x,
...obj2
}
But it didn't map correctly.
Thanks.
EDIT: Updated obj2 response.

You can array#concat both your array and then using array#reduce and an object lookup with id, merge your objects. Then return all the values from this object.
const obj1 = { data: [{ id: 1, name: 'Linda' }, { id: 2, name: 'Mark' }]},
obj2 = { data: [{ id: 1, salary: "2000, 60 USD"}, { id: 2, salary: "4000, 50 USD"}]},
result = Object.values(obj1.data.concat(obj2.data).reduce((r,o) => {
r[o.id] = r[o.id] || {};
r[o.id] = {...r[o.id], ...o};
return r;
},{}));
console.log(result);

You could take a Map for collecting all properties of the same id in an object. Later get the values of the map.
const join = o => o && map.set(o.id, Object.assign(map.get(o.id) || {}, o));
var obj1 = { data: [{ id: 1, name: 'Linda' }, { id: 2, name: 'Mark' } ]},
obj2 = [{ id: 1, salary: "2000, 60 USD" }, undefined, { id: 2, salary: "4000, 50 USD" }, undefined],
map = new Map,
result;
obj1.data.forEach(join);
obj2.forEach(join);
result = { data: Array.from(map.values()) };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Another way
const res = {
...obj1, // take the initial object
data: obj1.data.map(item => ({ // rewrite data property by iterate each item
// and merge item with corresponding
// object from obj2
...item, // take the item object
...obj2.find(({ id }) => id === item.id) // find corresponding object
}))
};

Here is an approach which would combine the objects and not overwrite the properties but only add the ones that are missing as well as avoid the undefined etc:
const names = {data: [{ id: 1, name: 'Linda' },{ id: 2, name: 'Mark' }]}
const salaries = [{ id: 1, salary: "2000, 60 USD" }, undefined]
var result = _.mapValues(names.data, x => {
let hit = _.find(salaries, y => y ? y.id === x.id : null)
return hit ? _.defaults(x, hit) : x
})
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script>
We are using mapValues to get to the values of names.data and look through them and for each of them get a hit in the salaries. If the hit exists we default the props of the hit with the current data object and return. Hope this helps.

Related

Compare and update two arrays without losing mutated data

I have an array of objects contains data of persons
const oldArr = [
{
id: 1,
name: 'Alex',
},
{
id: 2,
name: 'John',
},
{
id: 3,
name: 'Jack',
}
]
then I add data to this array to each element where I end up with new key called money with value of 20 as the following
oldArr.map((el, index) => el.money = 20)
and the array becomes like this
...
{
id: 2,
name: 'John',
money: 20
},
...
Now, I have a new array with new data (new person) but missing the money I have added before. (careful person with id 2 is not there)
const newArr = [
{
id: 1,
name: 'Alex',
},
{
id: 3,
name: 'Jack',
},
{
id: 4,
name: 'Chris',
},
]
I want to update the old array with new data but also keep the mutated data, and I want the result to end up like this:
const result = [
{
id: 1,
name: 'Alex',
money: 20
},
{
id: 3,
name: 'Jack',
money: 20
},
{
id: 4,
name: 'Chris',
},
]
Thanks for the help.
Just a note: map creates a whole new array, it doesn't make sense to use it for just mutating the contents. Use forEach or just a regular for loop instead.
oldArr.forEach((el) => (el.money = 20));
The following will give you the intended result:
const result = newArr.map(
(newEl) => oldArr.find((el) => el.id === newEl.id) || newEl
);
The OR operator || returns the second argument if the first is falsey.
You can optimize this by mapping items by id instead of brute force searching the old array.
const idMap = new Map();
oldArr.forEach((el) => {
el.money = 20;
idMap.set(el.id, el);
});
const result = newArr.map((newEl) => idMap.get(newEl.id) || newEl);
Stackblitz: https://stackblitz.com/edit/js-f3sw8w?file=index.js
If I getted it clear you are just trying to iterate throw the items of array generating a new array with the property "money" added to each one.
If so the map is the best option, just assign it to a new variable and change the item before return the element like bellow.
const oldArr = [
{
id: 1,
name: "Alex"
},
{
id: 2,
name: "John"
},
{
id: 3,
name: "Jack"
}
];
const newArr = oldArr.map((el) => {
el.money = "20";
return el;
});
console.log(oldArr);
console.log(newArr);
In this way you'll be able to keep both arrays.
If wasn't this, pls let me know.
Just merge the objects:
const result = oldArr.map((person) => ({
...person,
...newArr.find((cur) => cur.id === person.id),
}));

check array of objects if specific value is unique across all the array

I have an array like this
const arr = [{ name: 'sara' }, { name: 'joe' }];
and i want to check if name is unique across the array so
const arr = [{ name: 'sara' }, { name: 'joe' }];//sara is unique //true
const arr = [{ name: 'sara' }, { name: 'sara' },{ name: 'joe' }];//sara is unique //false
i know there's array.some but it doesn't help in my situation
whats is the best way to achieve that using javascript thanks in advance
You could take a single loop and a Set for seen value.
isUnique is a function which takes a property name and returns a function for Array#every.
const
isUnique = (key, s = new Set) => o => !s.has(o[key]) && s.add(o[key]),
a = [{ name: 'sara' }, { name: 'joe' }],
b = [{ name: 'sara' }, { name: 'sara' }, { name: 'joe' }];
console.log(a.every(isUnique('name'))); // true
console.log(b.every(isUnique('name'))); // false
i have this implementation and it dose the job
const arr = [{ name: "salah" }, { name: "joe" }];
let bols = [];
arr.forEach((item1, i1) => {
arr.forEach((item2, i2) => {
if (i1 !== i2) {
bols.push(item2.name === item1.name);
}
});
});
bols.some((item) => item === true);

How to merge two array of objects by key AND keep unique keys in a single array of objects?

I want to merge two array of objects where objects with the same ID will merge properties and objects with unique IDs will be its own object in the merged array. The following code does the first part where similar IDs will merge but how do I keep objects with unique ids from arr2 in the merged array and have it work with arrays of varying lengths?
Expected output:
[
{
"id": "1",
"date": "2017-01-24",
"name": "test"
},
{
"id": "2",
"date": "2017-01-22",
"bar": "foo"
}
{ "id": "3",
"foo": "bar",
}
]
The code:
let arr1 = [{
id: '1',
createdDate: '2017-01-24'
},
{
id: '2',
createdDate: '2017-01-22'
},
];
let arr2 = [{
id: '1',
name: 'test'
},
{
id: '3',
foo: 'bar'
},
{
id: '2',
bar: 'foo'
},
];
let merged = [];
for (let i = 0; i < arr1.length; i++) {
merged.push({
...arr1[i],
...arr2.find((itmInner) => itmInner.id === arr1[i].id),
},
);
}
console.log(merged);
Iterate over the larger array, the one that contains the smaller array, instead:
let arr1=[{id:"1",createdDate:"2017-01-24"},{id:"2",createdDate:"2017-01-22"}],arr2=[{id:"1",name:"test"},{id:"3",foo:"bar"},{id:"2",bar:"foo"}];
const merged = arr2.map(item => ({
...arr1.find(({ id }) => id === item.id),
...item
}));
console.log(merged);
(if order matters, you can sort if afterwards too)
If you don't know in advance which one / if one will contain the other, then use an object to index the merged objects by IDs first:
let arr1=[{id:"1",createdDate:"2017-01-24"},{id:"2",createdDate:"2017-01-22"}],arr2=[{id:"1",name:"test"},{id:"3",foo:"bar"},{id:"2",bar:"foo"}];
const resultObj = Object.fromEntries(
arr1.map(
item => [item.id, { ...item }]
)
);
for (const item of arr2) {
if (!resultObj[item.id]) {
resultObj[item.id] = item;
} else {
Object.assign(resultObj[item.id], item);
}
}
const merged = Object.values(resultObj);
console.log(merged);
You can create new object that contains the elems of arr1 and arr2 group by id key as follows and the merged array will be stored on object values.
You can get object values using Object.values func.
let arr1 = [{
id: '1',
createdDate: '2017-01-24'
},
{
id: '2',
createdDate: '2017-01-22'
},
];
let arr2 = [{
id: '1',
name: 'test'
},
{
id: '3',
foo: 'bar'
},
{
id: '2',
bar: 'foo'
},
];
const groupById = {};
for (let i = 0; i < Math.min(arr1.length, arr2.length); i ++) {
if (arr1[i]) {
groupById[arr1[i].id] = { ...groupById[arr1[i].id], ...arr1[i] };
}
if (arr2[i]) {
groupById[arr2[i].id] = { ...groupById[arr2[i].id], ...arr2[i] };
}
}
const merged = Object.values(groupById);
console.log(merged);
You could take a single loop approach by storing the objects in a hash table, sorted by id.
const
mergeTo = (target, objects = {}) => o => {
if (!objects[o.id]) target.push(objects[o.id] = {});
Object.assign(objects[o.id], o);
},
array1 = [{ id: '1', createdDate: '2017-01-24' }, { id: '2', createdDate: '2017-01-22' }],
array2 = [{ id: '1', name: 'test' }, { id: '3', foo: 'bar' }, { id: '2', bar: 'foo' }],
merged = [],
merge = mergeTo(merged);
array1.forEach(merge);
array2.forEach(merge);
console.log(merged);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A different approach could be merged the two array as is, and then "squash" it:
let arr1 = [{
id: '1',
createdDate: '2017-01-24'
},
{
id: '2',
createdDate: '2017-01-22'
},
];
let arr2 = [{
id: '1',
name: 'test'
},
{
id: '3',
foo: 'bar'
},
{
id: '2',
bar: 'foo'
},
];
let merged = [...arr1, ...arr2].reduce(
(acc, {id, ...props}) =>
(acc.set(id, {...(acc.get(id) || {}), ...props}), acc), new Map());
console.log([...merged].map( ([id, props]) => ({id, ...props}) ))
Notice that you might not need the last line, it used just to obtain the format you want to, since the above reduce is using a Map as accumulator, you can already access to everything with just merged.get("1").createdDate for example (where "1" is the id).
Since you're operating on one array by merging them at the beginning, you don't care about the length of them or even which one contains more elements. You can also have several arrays instead of just two, it doesn't matter.
What it matters is the order: if more than one array contains the same property for the same "id", the value you'll get is the value from the most recent array added (in the example above, would be arr2).
You can write a function to reduce the arrays to an object and then extract the value from that object which will return the values that you want. You can see the code below:
let arr1 = [
{
id: '1',
createdDate: '2017-01-24',
},
{
id: '2',
createdDate: '2017-01-22',
},
];
let arr2 = [
{
id: '1',
name: 'test',
},
{
id: '3',
foo: 'bar',
},
{
id: '2',
bar: 'foo',
},
];
function merge(arr1 = [], arr2 = []) {
return Object.values(
arr1.concat(arr2).reduce(
(acc, curr) => ({
...acc,
[curr.id]: { ...(acc[curr.id] ?? {}), ...curr },
}),
{}
)
);
}
const merged = merge(arr1, arr2);
Output:
[
{
"id": "1",
"createdDate": "2017-01-24",
"name": "test"
},
{
"id": "2",
"createdDate": "2017-01-22",
"bar": "foo"
},
{
"id": "3",
"foo": "bar"
}
]

Reduce and sort object array in one step

I'm trying to get two values (label and data) from my source data array, rename the label value and sort the label and data array in the result object.
If this is my source data...
const sourceData = [
{ _id: 'any', count: 12 },
{ _id: 'thing', count: 34 },
{ _id: 'value', count: 56 }
];
...the result should be:
{ label: ['car', 'plane', 'ship'], data: [12, 34, 56] }
So any should become car, thing should become plane and value should become ship.
But I also want to change the order of the elements in the result arrays using the label values, which should also order the data values.
Let's assume this result is expected:
{ label: ['ship', 'car', 'plane'], data: [56, 12, 34] }
With the following solution there is the need of two variables (maps and order). I thing it would be better to use only one kind of map, which should set the new label values and also the order. Maybe with an array?!
Right now only the label values get ordered, but data values should be ordered in the same way...
const maps = { any: 'car', thing: 'plane', value: 'ship' }; // 1. Rename label values
const result = sourceData.reduce((a, c) => {
a.label = a.label || [];
a.data = a.data || [];
a.label.push(maps[c._id]);
a.data.push(c.count);
return a;
}, {});
result.label.sort((a, b) => {
const order = {'ship': 1, 'car': 2, plane: 3}; // 2. Set new order
return order[a] - order[b];
})
You could move the information into a single object.
const
data = [{ _id: 'any', count: 12 }, { _id: 'thing', count: 34 }, { _id: 'value', count: 56 }],
target = { any: { label: 'car', index: 1 }, thing: { label: 'plane', index: 2 }, value: { label: 'ship', index: 0 } },
result = data.reduce((r, { _id, count }) => {
r.label[target[_id].index] = target[_id].label;
r.data[target[_id].index] = count;
return r;
}, { label: [], data: [] })
console.log(result);
Instead of separating the data into label and data and then sorting them together, you can first sort the data and then transform.
const sourceData = [
{ _id: 'any', count: 12 },
{ _id: 'thing', count: 34 },
{ _id: 'value', count: 56 }
];
const maps = { any: 'car', thing: 'plane', value: 'ship' };
// Rename label values.
let result = sourceData.map(item => ({
...item,
_id: maps[item._id]
}));
// Sort the data.
result.sort((a, b) => {
const order = {'ship': 1, 'car': 2, plane: 3};
return order[a._id] - order[b._id];
})
// Transform the result.
result = result.reduce((a, c) => {
a.label = a.label || [];
a.data = a.data || [];
a.label.push(c._id);
a.data.push(c.count);
return a;
}, {});
console.log(result);

Array.filter() to remove duplicate Ojects

I would like to fuse Array.filter() function to remove duplicate objects
I am able to achieve in the case of string or integer arrays. But I am not able to achieve the same with array of objects as in the second case of names
const names = ['John', 'Paul', 'George', 'Ringo', 'John'];
let x = names => names.filter((v, i, arr) => arr.indexOf(v) === i);
console.log(x(names)); //[ 'John', 'Paul', 'George', 'Ringo' ]
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" } ];
// returns the same original array
Could you please help?
Using Array#reduce() and a Map accumulator then spread the values() of the Map into array
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" } ];
const unique = [... names.reduce((a,c)=>(a.set(c.name,c)),new Map).values()]
console.log(unique)
Use Array.reduce and Object.values
Iterate over the array and create an object with key as name and value as object from array. In case of objects with same name, the value will be overwritten in resultant object. Finally use Object.values to collect all the unique objects.
const names = [{ name: "John" },{ name: "Paul" },{ name: "George" },{ name: "Ringo" },{ name: "John" } ];
let result = Object.values(names.reduce((a,c) => Object.assign(a, {[c.name]:c}),{}));
console.log(result);
For tweaking - Plunker
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" }
];
/* unique => Filter: Remove all duplicate items from an array. Works with plain objects as well, since we stringify each array item.
* #type public Function
* #name unique
* #return Function( item )
* #notes
*/
const unique = () => {
const seen = {};
return item => {
const json = JSON.stringify( item );
return seen.hasOwnProperty( json )
? false
: ( seen[ json ] = true );
};
};
const result = names.filter( unique() );
console.log( result );
You could use lodash's _uniqBy for this:
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" } ];
const result = _uniqBy(names, 'name');
This can be done with the help of Sets as well
var names = [{ name: "John" },{ name: "Paul" },{ name: "George" },{ name: "Ringo" },{ name: "John" } ];
var result = Array.from(
names.reduce((s, d) => s.add(d.name), new Set)
, d => ({ name: d })
)
console.log(result)
Keith had a great suggestion to use findIndex with filter instead of indexOf. Object literals are always unique references, so we cannot compare them. We can however compare the name keys between the objects. We can do this with the aforementioned functions.
const names = [
{ name: "John" },
{ name: "Paul" },
{ name: "George" },
{ name: "Ringo" },
{ name: "John" }
];
console.log(names.filter(({name1}, i, a) => {
return i == a.findIndex(({name2}) => {
return name1 == name2;
});
});
const names = ['John', 'Paul', 'George', 'Ringo', 'John'];
function removeDups(names) {
let unique = {};
names.forEach(function(i) {
if(!unique[i]) {
unique[i] = true;
}
});
return Object.keys(unique);
}
removeDups(names); //'John', 'Paul', 'George', 'Ringo'

Categories

Resources