Merge and concat two arrays of objects - JS - javascript

I have two array of objects and I want to merge them based on different properties and also concat if something doesnt exist in the list.
This is what I have:
const data1 = [{
name: 'A',
id: 1
}, {
name: 'B',
id: 2
}]
const data2 = [{
city: 'X',
rowID: 1
}, {
city: 'Y',
rowID: 2
}, {
city: 'Z',
rowID: 3
}]
const result = _.map(data1, function(p) {
return _.merge(
p,
_.find(data2, {
rowID: p.id
})
)
})
console.log(result)
//Expected Result
/**[
{
"name": "A",
"id": 1,
"city": "X",
"rowID": 1
},
{
"name": "B",
"id": 2,
"city": "Y",
"rowID": 2
},
{
"name": "",
"id": "",
"city": 'Z',
"rowID": 3
}
]*/
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>
Please advice.

You could collect the merged object with a Map and get the values.
const
merge = data => {
const
pattern = {},
map = new Map;
data.forEach(([objects, key]) => {
Object.keys(objects[0]).forEach(k => pattern[k] = '');
objects.forEach(o => map.set(o[key], { ...map.get(o[key]), ...o }));
});
return Array.from(map.values(), o => ({ ...pattern, ...o }));
},
data1 = [{ name: 'A', id: 1 }, { name: 'B', id: 2 }],
data2 = [{ city: 'X', rowID: 1 }, { city: 'Y', rowID: 2 }, { city: 'Z', rowID: 3 }],
result = merge([[data1, 'id'], [data2, 'rwoID']]);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Concat the arrays, group them by id or rowId, merge all objects and map the values to empty strings, map the object of groups, and merge each group with the defaults to an object (including default empty id and name).
const { concat, groupBy, mapValues, map, merge } = _
const fn = (getId, ...arrs) => {
const items = concat(...arrs);
const defaults = mapValues(merge({}, ...items), () => '');
return map(
groupBy(items, getId),
group => merge({}, defaults, ...group)
)
}
const data1 = [{"name":"A","id":1},{"name":"B","id":2}]
const data2 = [{"city":"X","rowID":1},{"city":"Y","rowID":2},{"city":"Z","rowID":3}]
const result = fn(o => o.id ?? o.rowID, data1, data2)
console.log(result)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.20/lodash.min.js"></script>

Related

Move objects in array where duplicates occur

I have an array of objects, each array has a key of name and then another array of objects:
const myArray = [ { name: "1", item: [{}] }, { name: "2", item: [{}] }, { name: "1", item: [{}] } ]
Now for example sometimes that name key will be the same, i want to be able to check if that name exists and if it does exist push the item into that array object and not into a new object.
The behaviour im getting is above but i would like:
const myArray = [ { name: "1", item: [{ item1, item2 etc }] }, { name: "2", item: [{}] }, { name: "3", item: [{}] } ]
Thanks so much in advance!
You can get the desired result using Array.reduce(), grouping by name.
If two objects in myArray share the same name, the item values are combined.
const myArray = [ { name: "1", item: [{ id: 1 }] }, { name: "2", item: [{ id: 2}] }, { name: "1", item: [{ id: 3}] } ]
const result = Object.values(myArray.reduce((acc, { name, item }) => {
acc[name] = acc[name] || { name, item: [] };
acc[name].item.push(...item);
return acc;
}, {}))
console.log('Result:', result)
.as-console-wrapper { max-height: 100% !important; }
Here's a solution using Array.prototype.reduce function.
const myArray = [ { name: "1", item: [{}] }, { name: "2", item: [{}] }, { name: "1", item: [{}] } ];
const output = myArray.reduce((acc, curr) => {
const index = acc.findIndex(pre => pre.name === curr.name);
if(index !== -1) {
acc[index].item = acc[index].item.concat(curr.item);
} else {
acc.push(curr);
}
return acc;
}, []);
console.log(output);

ES6 map.has is not a function when called in a reducer

I want to return an array of objects without any duplicate ids. If there are any, then take the first one we see. So, we should NOT see {id: "2", value: '10'}. Instead, the value should be "Italian". I have this code below, but I am getting an map.has is not a function error.
const arr1 = [{
id: "1",
value: "English"
},
{
id: "2",
value: "Italian"
}
];
const arr2 = [{
id: "2",
value: '10'
},
{
id: "3",
value: "German"
}
];
const concatArr = arr1.concat(arr2);
const mergedArr = [...concatArr.reduce((map, obj) => map.has(obj.id) ? "" : map.set(obj.id, obj), new Map()).values()];
console.log(mergedArr);
You need to always return a map not an empty string when the thing is already in the map.
const arr1 = [{
id: "1",
value: "English"
},
{
id: "2",
value: "Italian"
}
];
const arr2 = [{
id: "2",
value: '10'
},
{
id: "3",
value: "German"
}
];
const concatArr = arr1.concat(arr2);
const mergedArr = [...concatArr.reduce((map, obj) => map.has(obj.id) ? map : map.set(obj.id, obj), new Map()).values()];
console.log(mergedArr);
You can use array#reduce to uniquely identify each object with unique id in an object accumulator and then extract all values from this object using Object.values().
const arr1 = [{ id: "1", value: "English" }, { id: "2", value: "Italian" } ],
arr2 = [{ id: "2", value: '10' }, { id: "3", value: "German" } ],
result = Object.values(arr1.concat(arr2).reduce((r, o) => {
r[o.id] = r[o.id] || o;
return r;
},{}));
console.log(result);

Sort array by number from other array

I have two arrays and I want to sort first one based on some values from another array:
const items = [
['music', ['arr']],
['movies', ['arr']],
['quizes', ['arr']],
['series', ['arr']]
];
const categories = [
{ name: "music", priority: 3},
{ name: "movies", priority: 2},
{ name: "quizes", priority: 5},
{ name: "series", priority: 1},
{ name: "sports", priority: 4},
];
I want to sort my first array, by property 'priority' from my second array -> from the biggest one.
Like this:
const expectedResult = [
['quizes', ['arr']],
['music', ['arr']]
['movies', ['arr']],
['series', ['arr']],
];
This is what I tried but without success.
const sorted = items.sort((a,b) => {
const [aKey, aVal] = a;
const [bKey, bVal] = b;
const prio = categories.filter(c => c.name === aKey)[0];
// not sure how to use this prio
return aKey.priority - bKey.priority;
})
You were very close, you just needed to grab b's priority (and also use the priority property). find rather than filter is a good choice:
const sorted = items.sort((a,b) => {
const [aKey] = a;
const [bKey] = b;
const aPriority = categories.find(cat => cat.name === aKey).priority;
const bPriority = categories.find(cat => cat.name === bKey).priority;
return bPriority - aPriority;
});
Live Example
const items = [
["music", ["arr"]],
["movies", ["arr"]],
["quizes", ["arr"]],
["series", ["arr"]]
];
const categories = [
{ name: "music", priority: 3},
{ name: "movies", priority: 2},
{ name: "quizes", priority: 5},
{ name: "series", priority: 1},
{ name: "sports", priority: 4},
];
const sorted = items.sort((a,b) => {
const [aKey] = a;
const [bKey] = b;
const aPriority = categories.find(cat => cat.name === aKey).priority;
const bPriority = categories.find(cat => cat.name === bKey).priority;
return bPriority - aPriority;
});
console.log(sorted);
.as-console-wrapper {
max-height: 100% !important;
}
But repeatedly traversing that array of categories isn't a good idea if items is long. Instead, make a Map of key to priority, then use that:
const catPriorityMap = new Map(categories.map(({name, priority}) => [name, priority]));
const sorted = items.sort((a,b) => {
const [aKey] = a;
const [bKey] = b;
const aPriority = catPriorityMap.get(aKey);
const bPriority = catPriorityMap.get(bKey);
return bPriority - aPriority;
});
Live Example
const items = [
["music", ["arr"]],
["movies", ["arr"]],
["quizes", ["arr"]],
["series", ["arr"]]
];
const categories = [
{ name: "music", priority: 3},
{ name: "movies", priority: 2},
{ name: "quizes", priority: 5},
{ name: "series", priority: 1},
{ name: "sports", priority: 4},
];
const catPriorityMap = new Map(categories.map(({name, priority}) => [name, priority]));
const sorted = items.sort((a,b) => {
const [aKey] = a;
const [bKey] = b;
const aPriority = catPriorityMap.get(aKey);
const bPriority = catPriorityMap.get(bKey);
return bPriority - aPriority;
});
console.log(sorted);
.as-console-wrapper {
max-height: 100% !important;
}
Lookup in a Map is done in sublinear time, whereas finding somethign in an array is done in linear time.
You can use sort() method and check the priority from the categories
const items = [
['music', ['arr']],
['movies', ['arr']],
['quizes', ['arr']],
['series', ['arr']],
];
const categories = [
{ name: 'music', priority: 3 },
{ name: 'movies', priority: 2 },
{ name: 'quizes', priority: 5 },
{ name: 'series', priority: 1 },
{ name: 'sports', priority: 4 },
];
const result = items.sort(([a], [b]) => {
const aPriority = categories.find(({ name }) => name === a).priority;
const bPriority = categories.find(({ name }) => name === b).priority;
return bPriority - aPriority;
});
console.log(result);
Learn more about sort() here.
You really do not want to use find() or filter() inside of the sort method because it is expensive. On every iteration, you are looking up the data in the array. So you are looping a lot. There is better ways to get the index.
Easiest thing is to make a lookup object so you are not having to search the other array over and over for a match. So if you can change categories to an object from the start, it will make your life so much easier.
In the sort I added max value in case the key is not defined. Now this would backfire if you had a value of zero since it is just a truthy check.
const items = [
['music', ['arr']],
['movies', ['arr']],
['quizes', ['arr']],
['series', ['arr']]
];
const categories = {
music: 3,
movies: 2,
quizes: 5,
series: 1,
sports: 4,
};
items.sort(([keyA], [keyB]) => (categories[keyA] || Number.MAX_VALUE) - (categories[keyB] || Number.MAX_VALUE));
console.log(items);
If you can not make the object look like that and you have to use the array, you can convert it from the array to an object. That can be done a few ways. I like to use reduce.
const items = [
['music', ['arr']],
['movies', ['arr']],
['quizes', ['arr']],
['series', ['arr']]
];
const categories = [
{ name: "music", priority: 3},
{ name: "movies", priority: 2},
{ name: "quizes", priority: 5},
{ name: "series", priority: 1},
{ name: "sports", priority: 4},
];
const lookup = categories.reduce((acc, obj) => ({...acc, [obj.name]: obj.priority}), {});
items.sort(([keyA], [keyB]) => (lookup[keyA] || Number.MAX_VALUE) - (lookup[keyB] || Number.MAX_VALUE));
console.log(items);
Now if you are sure that all of the keys will exist in the categories, you can drop the max value
items.sort(([keyA], [keyB]) => lookup[keyA] - lookup[keyB];
You could take an object for the order and sort by delta.
const
items = [['music', ['arr']], ['movies', ['arr']], ['quizes', ['arr']], ['series', ['arr']]],
categories = [{ name: "music", priority: 3 }, { name: "movies", priority: 2 }, { name: "quizes", priority: 5 }, { name: "series", priority: 1 }, { name: "sports", priority: 4 }],
order = Object.fromEntries(
categories.map(({ name, priority }) => [name, priority])
);
items.sort(([a], [b]) => order[a] - order[b]);
console.log(items);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to merge object with jquery by id

I need to be able to concatenate two JavaScript objects like the following:
let arr1 = [
{"0": { id: "abdc4051", date: "2017-01-24" }},
{"1": { id: "abdc4052", date: "2017-01-22" }}
];
let arr2 = [
{"0": { category: "Sport", data: {code: "abdc4051", name: "ab"} } },
{"1": { category: "Others", data: {code: "abdc4052", name: "abc"} } }
];
Does anyone have a script for this or know of a built in way to do this?
I want the date to be added in the data on arr2 with the condition code equal to id
Your object shape makes this harder than it should be. Are you certain you want the sequential properties in each object, or is that an artifact of logging/poor parsing?
You'll need to work around them if you actually need them, in the snippet below using Object.values() in creating a Map from arr1, and using Object.entries() in the final map() call on arr2 to store the sequential key and then reintroduce it in the return after the merge logic.
const
arr1 = [{ "0": { id: "abdc4051", date: "2017-01-24" } }, { "1": { id: "abdc4052", date: "2017-01-22" } }],
arr2 = [{ "0": { category: "Sport", data: { code: "abdc4051", name: "ab" } } }, { "1": { category: "Others", data: { code: "abdc4052", name: "abc" } } }],
// create map of dates: Map(2) { 'abdc4051' => '2017-01-24', 'abdc4052' => '2017-01-22' }
dateMap = new Map(arr1.map(o => {
const [{ id, date }] = Object.values(o);
return [id, date];
})),
// map over arr2, get date from Map and add it to 'data' if it exists
result = arr2.map(o => {
const [[k, _o]] = Object.entries(o);
const date = dateMap.get(_o.data.code);
return {
[k]: {
..._o,
data: { ..._o.data, ...(date ? { date } : {}) }
}
};
});
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you don't need the initial sequential keys the merge becomes much less verbose.
const
arr1 = [{ id: "abdc4051", date: "2017-01-24" }, { id: "abdc4052", date: "2017-01-22" }],
arr2 = [{ category: "Sport", data: { code: "abdc4051", name: "ab" } }, { category: "Others", data: { code: "abdc4052", name: "abc" } }],
dateMap = new Map(arr1.map(o => [o.id, o.date])),
result = arr2.map(o => (
{
...o,
data: { ...o.data, ...(dateMap.has(o.data.code) ? { date: dateMap.get(o.data.code) } : {}) }
}
));
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }

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"
}
]

Categories

Resources