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; }
Related
I am having two arrays
const selected = [];
const current = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
const result = []
I need to compare these two arrays and the result should only have the single entry instead of duplicates. In the above example result should have the following output.
Also items in the selected should be taken into consideration and should be in the beginning of the result
result = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
Also when the input is following
const selected = [ {id:5, name: "xyz" }];
const current = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
result = [[
{ id: 5, name: "xyz" },
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
Also when the input is following
const selected = [ {id:1, name: "abc" }, {id:4, name: "lmn" }];
const current = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
result = [[
{ id: 1, name: "abc" },
{ id: 4, name: "lmn" }
{ id: 2, name: "def" }
];
Note the comparison should be made using name field
Code that I tried
const res = [...(selected || [])].filter((s) =>
current.find((c) => s.name === c.name)
);
Sandbox: https://codesandbox.io/s/nervous-shannon-j1vn5k?file=/src/index.js:115-206
You could get all items and filter the array by checking the name with a Set.
const
filterBy = (key, s = new Set) => o => !s.has(o[key]) && s.add(o[key]),
selected = [{ id: 1, name: "abc" }, { id: 1, name: "lmn" }],
current = [{ id: 1, name: "abc" }, { id: 2, name: "def" }],
result = [...selected, ...current].filter(filterBy('name'));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Loop through selected, and if there is no object in current with a name that matches the name of the object in the current iteration push it into current.
const selected=[{id:1,name:"abc"},{id:6,name:"def"},{id:4,name:"lmn"}];
const current=[{id:1,name:"abc"},{id:2,name:"def"}];
for (const sel of selected) {
const found = current.find(cur => cur.name === sel.name);
if (!found) current.push(sel);
}
console.log(current);
This is a good use for .reduce, avoids multiple loops/finds and doesn't need filtering with side-effects.
const selected = [ {id:1, name: "abc" }, {id:4, name: "lmn" }];
const current = [
{ id: 1, name: "abc" },
{ id: 2, name: "def" }
];
const result = Object.values(
[...selected, ...current].reduce((obj, item) => {
obj[item.name] = obj[item.name] || item;
return obj;
}, {})
)
console.log(result);
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);
I have such a case:
const ids = {
// id: parentId
"2": "1",
"3": "1"
}
const input = [
{
id: "1",
data: [1],
},
{
id: "2",
data: [2]
},
{
id: "3",
data: [3]
},
{
id: "4",
data: [4]
}
]
const output = [
{
id: "1",
data: [1,2,3]
},
{
id: "4",
data: [4]
}
]
With this ids I wanted to create some kind of config, which will determine which object in input array should have merged data (if there is no id-parentId pair defined, it should stay as it is). I belive code above is better explanation than this story. I tried with some mappings but without any nice result. This 'ids' array is an example, maybe there is a better way to define it so it will simplify the case? What do you think? I will be grateful for any hint
You can use reduce(). Create the output first as an object that uses id as the key. You can convert it to an array at the end with Object.values().
const ids = {
// id: parentId
"2": "1",
"3": "1"
}
const input = [{
id: "1",
data: [1],
},
{
id: "2",
data: [2]
},
{
id: "3",
data: [3]
},
{
id: "4",
data: [4]
}
]
const output = Object.values(input.reduce((acc, {
id,
data
}) => {
if (id in ids) { // replace id with parent
id = ids[id];
}
if (id in acc) {
acc[id].data = acc[id].data.concat(data);
} else {
acc[id] = {
id,
data};
}
return acc;
}, {}));
console.log(output);
You could take the references for same groups.
const
ids = { 2: "1", 3: "1" },
input = [{ id: "1", data: [1] }, { id: "2", data: [2] }, { id: "3", data: [3] }, { id: "4", data: [4] }],
output = Object.values(input.reduce((r, { id, data }) => {
id = ids[id] ?? id;
(r[id] ??= { id, data: [] }).data.push(...data);
return r;
}, {}));
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can try to cycle the input and modifying it in place, something like this should work:
const input = [
{
id: '1',
data: [1]
},
{
id: '2',
data: [2]
},
{
id: '3',
data: [3]
},
{
id: '4',
data: [4]
}
]
const config = {
2: '1',
3: '1'
}
const merge = ({ input, config } = {}) => {
for (const [childId, parentId] of Object.entries(config)) {
const childIndex = input.findIndex(i => i.id == childId)
const parent = input.find(i => i.id == parentId)
parent.data.push(...input[childIndex].data)
input.splice(childIndex, 1)
}
return input
}
console.log(merge({ input, config }))
you could do it like this:
let ids = { 2: "1", 3: "1" }
let input = [{ id: "1", data: [1] }, { id: "2", data: [2] }, { id: "3", data: [3] }, { id: "4", data: [4] }];
let keys = Object.keys(ids);
let objs = input.slice();//in order to not mutate the source input
const output = objs.map(currObj=>{
let currId = currObj.id;
if(keys.includes(currId)){
let changeObj = objs.find(obj=>obj.id==ids[currId])
changeObj.data.push(...currObj.data)
}
return currObj
})
console.log(output);
NOTE: I used slice() in order to not mutate the source input
You can use Array#reduce, Object.entries and Array#map methods as follows:
const ids = { "2": "1", "3": "1" },
input = [ { id: "1", data: [1], }, { id: "2", data: [2] }, { id: "3", data: [3] }, { id: "4", data: [4] } ],
output = Object.entries(
input.reduce(
(acc,{id,data}) =>
ids[id] ?
({...acc,[ids[id]]:[...(acc[ids[id]] || []),...data]}) :
({...acc,[id]:[...(acc[id] || []), ...data]}),{}
)
)
.map(([id,data]) => ({id,data}));
console.log( output );
//OUTPUT: [{"id":"1","data":[1,2,3]},{"id":"4","data":[4]}]
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"
}
]
I have the next code:
const arr = [
{
name:'john',
cars:[
{audi:1},
{bmw:2}
]
},
{
name:'bill',
cars:[
{audi:10},
{bmw:0}
]
}
]
const arr1 = arr.map(i => {
if(i.name === 'john') {
return i.cars.map( a => {
return {
...i,
test:[2]
}
})
}
return i
})
console.log(arr1)
Here i want too loop through the array and for the first object to change the cars array, adding test:[2]. For this i used:
const arr1 = arr.map(i => {
if(i.name === 'john') {
return i.cars.map( a => {
return {
...i,
test:[2]
}
})
}
return i
})
The issue is that my code don't return what i want. I get the first object like:
0: Object
name: "john"
cars: Array[2]
test: 2
1: Object
name: "john"
cars: Array[2]
test: 2
but i need like this:
{
name:'john',
cars:[
{
audi:1,
test: [2],
},
{bmw:2}
]
},
How to solve my issue?
Since you only want to change the first item in the cars array, I don't think map is right - instead, just list the first changed car as an object literal inside an array, then spread the remaining cars into the array with .slice(1):
const arr = [
{
name:'john',
cars:[
{audi:1},
{bmw:2}
]
},
{
name:'bill',
cars:[
{audi:10},
{bmw:0}
]
}
]
const arr1 = arr.map(person => (
person.name !== 'john'
? person
: ({
name: person.name,
cars: [
{ ...person.cars[0], test: [2] },
...person.cars.slice(1)
]
})
));
console.log(arr1)
You could address the right position and add the wanted property.
const
data = [{ name: 'john', cars: [{ audi: 1 }, { bmw: 2 }] }, { name: 'bill', cars: [{ audi: 10 }, { bmw: 0 }] }],
add = { target: [0, 0], value: { test: [2] } }
result = data.map((o, i) => i === add.target[0]
? { ...o, cars: o.cars.map((p, j) => j === add.target[1]
? {... p, ...add.value }
: p)
}
: o);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Given the desired result, a non mapping solution may be viable?
const arr = [{
name: 'john',
cars: [{
audi: 1
},
{
bmw: 2
}
]
},
{
name: 'bill',
cars: [{
audi: 10
},
{
bmw: 0
}
]
}
];
// clone the initial arr
const arrModified = Object.assign([], arr);
// find John
const indexJohn = arrModified.findIndex(v => v.name === "john");
if (indexJohn > -1) {
// modify the desired value
arrModified[indexJohn].cars[0].test = [2];
}
console.log(arrModified[0].cars);
.as-console-wrapper { top: 0; max-height: 100% !important; }