JS (ES6): Merge arrays based on id and concatenating sub arrays - javascript

I have two arrays, which look like this:
const persons = [
{
id: 1,
name: 'Peter',
job: 'Programmer'
},
{
id: 2,
name: 'Jeff',
job: 'Architect'
},
];
const salaries = [
{
id: 1,
salary: 3000,
departments: ['A', 'B']
},
{
id: 1,
salary: 4000,
departments: ['A', 'C']
},
{
id: 2,
salary: 4000,
departments: ['C', 'D']
}
];
Now I need to somehow merge this arrays to one, so that every id only exists once. Same keys should be replaced, except it is an array, then I want them to add/concat. So the desired result should look something like this:
const result = [
{
id: 1,
name: 'Peter',
job: 'Programmer',
salary: 4000,
departments: ['A', 'B', 'C']
},
{
id: 2,
name: 'Jeff',
job: 'Architect',
salary: 4000,
departments: ['C', 'D']
}
];
I have already tried:
// double id's, arrays get replaced
Object.assign({}, persons, salaries)
// loadsh: double id's, arrays get concatenated
_.mergeWith(persons, salaries, (objValue, srcValue) => {
if (_.isArray(objValue)) {
return objValue.concat(srcValue);
}
});
// gives me a map but replaces arrays
new Map(salaries.map(x => [x.id, x])
Does anyone have an idea how to accomplish this?

You can use map(), filter(), reduce(), Object.assign() and Spread syntax to achieve required result.
DEMO
const persons = [{
id: 1,
name: 'Peter',
job: 'Programmer'
}, {
id: 2,
name: 'Jeff',
job: 'Architect'
}],
salaries = [{
id: 1,
salary: 3000,
departments: ['A', 'B']
}, {
id: 1,
salary: 4000,
departments: ['A', 'C']
}, {
id: 2,
salary: 4000,
departments: ['C', 'D']
}];
let output = persons.map(obj => {
let filter = salaries.filter(v => v.id == obj.id);
if (filter) {
let departments = filter.reduce((r, v) => [...v.departments, ...r], []);
Object.assign(obj, {
salary: filter[filter.length - 1].salary,
departments: departments.filter((item, pos) => departments.indexOf(item) == pos).sort()
});
}
return obj;
});
console.log(output)

You can concat the arrays, than combine all items with the same id using Array.reduce(), and a Map.
to combine objects with the same id, get the object from the Map. Iterate the new Object.entries() with Array.forEach(). Check if existing value is an array, if not assign the value. If it is an array, combine the arrays, and make the items unique using a Set with array spread.
To convert the Map back to an array, you can spread the Map.values() iterator.
const persons = [{"id":1,"name":"Peter","job":"Programmer"},{"id":2,"name":"Jeff","job":"Architect"}];
const salaries = [{"id":1,"salary":3000,"departments":["A","B"]},{"id":1,"salary":4000,"departments":["A","C"]},{"id":2,"salary":4000,"departments":["C","D"]}];
const result = [...persons.concat(salaries)
.reduce((r, o) => {
r.has(o.id) || r.set(o.id, {});
const item = r.get(o.id);
Object.entries(o).forEach(([k, v]) =>
item[k] = Array.isArray(item[k]) ?
[...new Set([...item[k], ...v])] : v
);
return r;
}, new Map()).values()];
console.log(result);

You could use a Map and iterate all properties and check the type for adding unique values to the arrays.
var persons = [{ id: 1, name: 'Peter', job: 'Programmer' }, { id: 2, name: 'Jeff', job: 'Architect' }],
salaries = [{ id: 1, salary: 3000, departments: ['A', 'B'] }, { id: 1, salary: 4000, departments: ['A', 'C'] }, { id: 2, salary: 4000, departments: ['C', 'D'] }],
result = Array.from(
salaries
.reduce(
(m, o) => {
var t = m.get(o.id) || {};
Object.keys(o).forEach(k => {
if (Array.isArray(o[k])) {
t[k] = t[k] || [];
o[k].forEach(v => t[k].includes(v) || t[k].push(v));
} else if (t[k] !== o[k]) {
t[k] = o[k];
}
});
return m;
},
persons.reduce((m, o) => m.set(o.id, Object.assign({}, o)), new Map)
)
.values()
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

How to get the intersection of two sets while recognizing equal set values/items not only by reference but by their equal structures and entries too?

I have two deal with two Set instances.
const set1 = new Set([
{ name: 'a' },
{ name: 'b', lastname: 'bb' },
{ name: 'c' },
{ name: 'd' },
]);
const set2 = new Set([
{ name: 'b' },
{ name: 'd' },
]);
Any object within a set will feature several and also distinct keys and values. The goal is to find structurally equal objects (same keys and values) in both sets, which is ... The intersection of equal data items in/of set1 and set2.
In the following example the expected result is [ { name: 'd' } ] ...
console.log([...set1].filter(item => set2.has(item)));
... but it logs an empty array / [] instead.
An object features more than 20 keys so one has to compare them one by one, which can not be done in a hard coded way.
How could one achieve a generic approach for an intersection of two lists of structurally equal data items?
You can do something like this:
const set1 = new Set([
{name: 'a'},
{name: 'b', lastname: 'bb'},
{name: 'c'},
{name: 'd'}
]);
const set2 = new Set([
{name: 'b'},
{name: 'd'}
]);
set1.forEach((value) => {
if (![...set2].some((o) => Object.entries(o).every(([k, v], _, arr) => (Object.keys(value).length === arr.length && value[k] === v)))) {
set1.delete(value);
}
})
console.log([...set1]);
What this does, is to iterate through set1 and if the item at the current iteration is not the same as any item in set2 (![...set2].some(..)), it is deleted.
The items are considered the same if they have the same number of keys and if the values at the same key are strictly equal.
This only works if the values of the objects in the sets are primitives, if they are not, you'll have to change value[k] === v to an appropriate comparison.
One could write a generic solution which compares pure, thus JSON conform, data structures regardless of any object's nesting depth/level and (creation time) key order.
Such a function would be self recursive for Array item (order matters) and Object property (key order does not matter) comparison. Otherwise values are compared strictly.
function isDeepDataStructureEquality(a, b) {
let isEqual = Object.is(a, b);
if (!isEqual) {
if (Array.isArray(a) && Array.isArray(b)) {
isEqual = (a.length === b.length) && a.every(
(item, idx) => isDeepDataStructureEquality(item, b[idx])
);
} else if (
a && b
&& (typeof a === 'object')
&& (typeof b === 'object')
) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
isEqual = (aKeys.length === bKeys.length) && aKeys.every(
(key, idx) => isDeepDataStructureEquality(a[key], b[key])
);
}
}
return isEqual;
}
const objA = { // `objA` equals `objB`.
name: 'foo',
value: 1,
obj: {
z: 'z',
y: 'y',
a: {
name: 'bar',
value: 2,
obj: {
x: 'x',
w: 'w',
b: 'b',
},
arr: ['3', 4, 'W', 'X', {
name: 'baz',
value: 3,
obj: {
k: 'k',
i: 'i',
c: 'c',
},
arr: ['5', 6, 'B', 'A'],
}],
},
},
arr: ['Z', 'Y', 1, '2'],
};
const objB = { // `objB` equals `objA`.
arr: ['Z', 'Y', 1, '2'],
obj: {
z: 'z',
y: 'y',
a: {
obj: {
x: 'x',
w: 'w',
b: 'b',
},
arr: ['3', 4, 'W', 'X', {
obj: {
k: 'k',
i: 'i',
c: 'c',
},
name: 'baz',
value: 3,
arr: ['5', 6, 'B', 'A'],
}],
name: 'bar',
value: 2,
},
},
name: 'foo',
value: 1,
};
const objC = { // `objC` equals neither `objA` nor `objB`.
arr: ['Z', 'Y', 1, '2'],
obj: {
z: 'z',
y: 'y',
a: {
obj: {
x: 'x',
w: 'w',
b: 'b',
},
arr: ['3', 4, 'W', 'X', {
obj: {
k: 'k',
i: 'i',
c: 'C', // the single difference to `objA` and `objB`.
},
name: 'baz',
value: 3,
arr: ['5', 6, 'B', 'A'],
}],
name: 'bar',
value: 2,
},
},
name: 'foo',
value: 1,
};
console.log(
'isDeepDataStructureEquality(objA, objB) ?..',
isDeepDataStructureEquality(objA, objB)
);
console.log(
'isDeepDataStructureEquality(objA, objC) ?..',
isDeepDataStructureEquality(objA, objC)
);
console.log(
'isDeepDataStructureEquality(objB, objC) ?..',
isDeepDataStructureEquality(objB, objC)
);
console.log(
'isDeepDataStructureEquality(objB, objA) ?..',
isDeepDataStructureEquality(objB, objA)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Based on the above implementation of isDeepDataStructureEquality one can solve the OP's task, that actually looks for the intersection of two list structures, by additionally providing a getIntersectionOfDeeplyEqualDataStructures functionality ...
function getIntersectionOfDeeplyEqualDataStructures(a, b) {
return [...(a ?? [])]
.reduce((collector, sourceItem) => {
const { target, intersection } = collector;
const targetIndex = target.findIndex(targetItem =>
isDeepDataStructureEquality(targetItem, sourceItem)
);
if (targetIndex >= 0) {
// collect the intersection of
// both, source (a) and target (b).
intersection.push(target[targetIndex]);
}
return collector;
}, {
target: [...(b ?? [])],
intersection: [],
}).intersection;
}
const set1 = new Set([
{ name: 'a' },
{ name: 'b', lastname: 'bb' },
{ name: 'c' },
{ name: 'd' }
]);
const set2 = new Set([
{ name: 'b' },
{ name: 'd' },
]);
console.log(
"getIntersectionOfDeeplyEqualDataStructures(set1, set2) ...",
getIntersectionOfDeeplyEqualDataStructures(set1, set2)
);
const set3 = new Set([
{ name: 'a' },
{ name: 'b', lastname: 'bb' },
{ name: 'c' },
{
name: 'd',
list: ['foo', 1, null, false, 0, {
foo: { bar: { baz: 'bizz', buzz: '' } }
}],
},
]);
const set4 = new Set([
{
list: ['foo', 1, null, false, 0, {
foo: { bar: { buzz: '', baz: 'bizz' } }
}],
name: 'd',
},
{ name: 'C' },
{ lastname: 'bb', name: 'b' },
{ name: 'aa' }
]);
console.log(
"getIntersectionOfDeeplyEqualDataStructures(set3, set4) ...",
getIntersectionOfDeeplyEqualDataStructures(set3, set4)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
function isDeepDataStructureEquality(a, b) {
let isEqual = Object.is(a, b);
if (!isEqual) {
if (Array.isArray(a) && Array.isArray(b)) {
isEqual = (a.length === b.length) && a.every(
(item, idx) => isDeepDataStructureEquality(item, b[idx])
);
} else if (
a && b
&& (typeof a === 'object')
&& (typeof b === 'object')
) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
isEqual = (aKeys.length === bKeys.length) && aKeys.every(
(key, idx) => isDeepDataStructureEquality(a[key], b[key])
);
}
}
return isEqual;
}
</script>
Edit
As for Titus' approach ...
set1.forEach(value => {
if (
![...set2].some(o =>
Object.entries(o).every(([k, v], _, arr) =>
(Object.keys(value).length === arr.length && value[k] === v)
)
)
) {
set1.delete(value);
}
});
... which works for flat objects only, though already agnostic to key insertion order, one could optimize the code by ...
... not creating the keys array of the most outer currently processed object again and again with every nested some and every iteration.
thus, something like ... const valueKeys = Object.keys(value); ... before the if clause, already helps improving the code.
... inverting the nested some and every logic which does result in a more efficient way of ... deleting every flat data-item from the processed set which does not equal any flat data-item from the secondary set.
On top of that, one could implement a function statement which not only helps code-reuse but also makes the implementation independent from outer scope references.
For instance, the primary set which is operated and going to be mutated can be accessed as such a function's third parameter. But most important for outer scope independency is the also available thisArg binding for any set's forEach method. Thus any function statement or function expression can access e.g. the other/secondary set via this in case the latter was passed as the forEach's 2nd parameter.
Also an improved wording supports a better readability of the code ...
//the function naming of cause is exaggerated.
function deleteItemFromSourceWhichDoesNotEqualAnyItemFromBoundTarget(sourceItem, _, sourceSet) {
const targetSet = this;
const sourceKeys = Object.keys(sourceItem);
if (
// ... for any data-item from the (bound) target-set ...
[...targetSet].every(targetItem =>
// ... which does not equal the currently processed data-item from the source-set ...
Object.entries(targetItem).some(([targetKey, targetValue], _, targetEntries) =>
sourceKeys.length !== targetEntries.length || sourceItem[targetKey] !== targetValue
)
)
) {
// ... delete the currently processed data-item from the source-set.
sourceSet.delete(sourceItem);
}
}
const set1 = new Set([
{ name: 'a' }, // - to be kept.
{ name: 'b', lastname: 'bb' }, // - to be kept.
{ name: 'c' }, // - to be deleted.
{ name: 'd', nested: { name: 'a' } }, // - to be kept, but fails ...
]); // ... due to not being flat.
const set2 = new Set([
{ name: 'd', nested: { name: 'a' } }, // - should equal, but doesn't.
{ name: 'a' }, // - does equal.
{ lastname: 'bb', name: 'b' }, // - does equal.
{ name: 'e' }, // - doesn't equal.
]);
// `set1` is going to be mutated.
set1.forEach(deleteItemFromSourceWhichDoesNotEqualAnyItemFromBoundTarget, set2);
console.log(
'mutated `set1` now (almost) being equal to the intersection of initial `set1` and `set2` ...',
[...set1]
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
const set1 = new Set([
{name: 'a'},
{name: 'b', lastname: 'bb'},
{name: 'c'},
{name: 'd'}
]);
const set2 = new Set([
{name: 'b'},
{name: 'd'}
]);
const names = [...set2].map(s2 => s2.name);
console.log([...set1].filter(item => names.includes(item.name)));
const set1 = new Set([
{name: 'a'},
{name: 'b', lastname: 'bb'},
{name: 'c'},
{name: 'd'},
{name: 'e'}
]);
const set2 = new Set([
{name: 'c', lastname: 'ccc'},
{name: 'd'},
{name: 'b', lastname: 'cc'},
{name: 'e'}
]);
console.log([...set1].filter(item => {
const s2Arr = [...set2];
const itemKeys = Object.keys(item);
for(let i = 0; i < s2Arr.length; i++){
const s2Obj = s2Arr[i];
const s2ObjKeys = Object.keys(s2Obj);
if(s2ObjKeys.length == itemKeys.length){
let oneSame = true;
for(let j = 0; j < s2ObjKeys.length; j++){
const s2ObjKey = s2ObjKeys[j];
if(item[s2ObjKey] != s2Obj[s2ObjKey]){
oneSame = false;
}
}
if(oneSame)
return true;
}
}
return false;
}));

JS/ES6/lodash find index of missing elements of two multidimensional arrays

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.

Getting exact Matching elements between two arrays

I have two arrays as follows:
array1 = [
{id:1, children: ['a', 'b']},
{id:2, children: ['a', 'b']},
{id:3, children: ['b', 'c']},
{id:4, children: ['c', 'a']},
{id:5, children: ['a', 'b', 'c']}];
array2 = ['a', 'b'];
Now I want to write a code in JS/TS which will find the exact objects from array1 where every element of children array from array 2 matches exactly with every elements of children array from array 1 (Order doesn't matter).
I have tried to solve this problem with three filters with additional condition of length matching of children array between array 1 and array2. But this also picks up if at least one element gets matched of those children array with desired array length.
I would really appreciate if someone gives me the solution.
array1
.filter(a => a.children
.filter(b => array2
.filter(c => b === c)).length === array2.length);
Edit:
I had actually simplified the problem a bit in the above example. In my actual project, the the two arrays are as follows:
const productOrders: ProductOrder[] =
[
{
productId: 1, subProductOrders:
[{subProduct: {subProductId: 1}}, {subProduct:
{subProductId: 2}}]
},
{
productId: 1, subProductOrders:
[{subProduct: {subProductId: 2}}, {subProduct:
{subProductId: 1}}]
},
{
productId: 1, subProductOrders:
[{subProduct: {subProductId: 2}}, {subProduct:
{subProductId: 3}}]
},
{
productId: 1, subProductOrders:
[{subProduct: {subProductId: 1}}, {subProduct:
{subProductId: 2}}, {subProduct: {subProductId: 3}}]
},
];
const matchingCriteria: SubProductOrder[] =
[
[{subProduct: {subProductId: 1}}, {subProduct: {subProductId:
2}}]
];
Now I want to find the products from the productOrders array where subProductId of the subProductOrders array matches with the subProductId of the matchingCriteria Array (Order doesn't matter). In the above example, the first two products of the productOrders Array should match despite unordered subProductsIds
You could take a Set and check the children against this structure.
var array1 = [{ id: 1, children: ['a', 'b'] }, { id: 2, children: ['a', 'b'] }, { id: 3, children: ['b', 'c'] }, { id: 4, children: ['c', 'a'] }, { id: 5, children: ['a', 'b', 'c'] }],
array2 = ['a', 'b'],
set2 = new Set(array2),
result = array1.filter(({ children }) =>
children.length === set2.size && children.every(Set.prototype.has, set2));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
For a more complex data structure, you could destructure the needed parts and check against a set of subProductId.
const
getId = ({ subProduct: { subProductId } }) => subProductId;
var productOrders = [{ productId: 1, subProductOrders: [{ subProduct: { subProductId: 1 } }, { subProduct: { subProductId: 2 } }] }, { productId: 1, subProductOrders: [{ subProduct: { subProductId: 2 } }, { subProduct: { subProductId: 1 } }] }, { productId: 1, subProductOrders: [{ subProduct: { subProductId: 2 } }, { subProduct: { subProductId: 3 } }] }, { productId: 1, subProductOrders: [{ subProduct: { subProductId: 1 } }, { subProduct: { subProductId: 2 } }, { subProduct: { subProductId: 3 } }] }],
matchingCriteria = [{ subProduct: { subProductId: 1 } }, { subProduct: { subProductId: 2 } }],
set2 = new Set(matchingCriteria.map(getId)),
result = productOrders.filter(({ subProductOrders }) =>
subProductOrders.length === set2.size &&
subProductOrders.every(o => set2.has(getId(o)))
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Array#every() and length in a single filter
const arr1 = [
{id:1, children: ['a', 'b']},
{id:2, children: ['a', 'b']},
{id:3, children: ['b', 'c']},
{id:4, children: ['c', 'a']},
{id:5, children: ['a', 'b', 'c']}
];
const arr2 = ['a', 'b'];
const matched = arr1.filter(({children: c}) => c.length === arr2.length && arr2.every(v => c.includes(v)))
console.log(matched)
array1 = [
{id:1, children: ['a', 'b']},
{id:2, children: ['b', 'a']},
{id:3, children: ['b', 'c']},
{id:4, children: ['c', 'a']},
{id:5, children: ['a', 'b', 'c']}];
array2 = ['a', 'b'];
const x = array1
.map(a => a.children.sort())
.filter(a => a.length === array2.length)
.map(a => a.sort())
.filter(a => JSON.stringify(a)==JSON.stringify(array2))
console.log(x)
You are filtering array1 elements based on the equality of element.children to array2.
so i encourage you to take a look at these answers. as it discuss the equality of two arrays in javascript.
and modify the following code to your needs, this is just one of the easiest options available:
array1.filter((element,index) => {
return JSON.stringify(element.children) === JSON.stringify(array2)
})

Looking to group array by values in the sub array

Trying to parse one data set that has a bunch of the same "secondaryIDs" in way that i can group and iterate through them together.
In english what im trying to do is
"select a unique group of all items where the value of field is unique "
'use strict';
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
let unique = [...new Set(data.map(item => item.Group))];
console.log(unique);
Which gives ["A"],["B"]
but what im looking for is
{
A: [ "SD","MM" ],
B: [ "FI","CO" ],
}
For this, I would use array.reduce instead of array.map because what you're actually hoping to return is a new value, not a modified array, the reduce method is perfect when you want to literally reduce the array into a single output value, in your case an object of unique groups. Maybe try something like this:
let unique = data.reduce((acc, { Group, Name }) => {
if (!(acc.hasOwnProperty(Group))) {
acc[Group] = [Name];
} else {
acc[Group].push(Name);
};
return acc;
}, {});
I've also added a pen for this at: https://codepen.io/anon/pen/BGpgdz?editors=1011 so you can see this working.
Hope this helps!
You can also reduce your array to the grouped object (keyed by Group values):
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
const grouped = data.reduce((a, {Group, Name}) => {
if (!(Group in a)) a[Group] = [Name];
else a[Group].push(Name);
return a;
}, {});
console.log(grouped);
can do something like..
const map = {};
data.forEach( d => {
if( map[d.Group] ) {
map[d.Group].push(d.Name);
} else {
map[d.Group] = [d.Name];
}
})
console.log(map)
I think the easiest way to achieve this would be to use Array.prototype.reduce method to create an object that maps unique Group names to arrays that contain Names. You can supply an empty object literal as your initial reduce accumulator:
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
var namesByGroup = data.reduce((map, el) => {
map[el.Group] ? map[el.Group].push(el.Name) : map[el.Group] = [el.Name];
return map;
}, {});
console.log(namesByGroup);
If you're interested in a functional approach, here is a solution using Ramda:
const group =
R.pipe(
R.groupBy(R.prop('Group')),
R.map(R.map(R.prop('Name'))));
console.log(
group([
{Group: 'A', Name: 'SD'},
{Group: 'B', Name: 'FI'},
{Group: 'A', Name: 'MM'},
{Group: 'B', Name: 'CO'}])
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
can also be done using forEach
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
const somefunction = (data) => {
let arr = {}
data.forEach( ({Group, Name}) => {
Group in arr ? arr[Group].push(Name) : arr[Group] = [Name]
})
return arr;
}
console.log(somefunction(data))

Merge property from an array of objects into another based on property value lodash

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 }
});

Categories