I have multi nested array of objects, in which I need to merge objects in the grandChildren based on the id without mutation
Details ......
###Example
let arr1 = {
"initiateLevel": true,
"parent": [
{
"name": "level1",
"childrens": [
{
"group": "Level-group",
"grandChildrens": [
{
"id": 21,
"technology": "sp1",
"path": "l2"
},
{
"id": 22,
"technology": "sp2",
"path": "l2"
}
]
}
]
},
{
"name": "level2",
"childrens": [
{
"group": "Level-group-2",
"grandChildrens": [
{
"id": 121,
"technology": "sp12",
"path": "l4"
},
{
"id": 122,
"technology": "sp22",
"path": "l4"
}
]
}
]
}
]
}
ex: Below object needs to merged with the array based on the id
let newobj=
[
{
"id": 22,
"reason": "reason 2",
"phase": "phase 2",
"reviewer": "by user 2",
"date": "date 2"
},
{
"id": 21,
"reason": "reason 1",
"phase": "phase 1",
"reviewer": "by user 1",
"date": "date 1"
}
]
expected output:
{
"initiateLevel": true,
"parent": [
{
"name": "level1",
"childrens": [
{
"group": "Level-group",
"grandChildrens": [
{
"id": 21,
"technology": "sp1",
"path": "l2",
"reason": "reason 1",
"phase": "phase 1",
"reviewer": "by user 1",
"date": "date 1"
},
{
"id": 22,
"technology": "sp2",
"path": "l2",
"reason": "reason 2",
"phase": "phase 2",
"reviewer": "by user 2",
"date": "date 2"
}
]
}
]
},
{
"name": "level2",
"childrens": [
{
"group": "Level-group-2",
"grandChildrens": [
{
"id": 121,
"technology": "sp12",
"path": "l4"
},
{
"id": 122,
"technology": "sp22",
"path": "l4"
}
]
}
]
}
]
}
I tried to like this. but it's not working
const merge = (y, z) => {
y.parent.forEach((element) => {
element.childrens.forEach((x) => {
x.grandChildrens.forEach((test) => {
const reviewIndex = z.findIndex(
(reviewItem) => reviewItem.id === test.id
);
if(reviewIndex>=0)
{
return {...test, ...z[reviewIndex]}
}
});
});
});
};
merge(arr1,newobj)
How to merge the object based on the id without mutation.
CodePudding user response:
From question:
How to merge the object based on the id without mutation.
Simple answer: "NO". It is not possible to "merge" (ie, update) an object without updating (ie, mutating).
Alternative solution: Create a copy (preferably deep-clone) of the object which will be updated / merged. Thus, the original object will remain unmodified.
Presented below is one possible way to achieve the alternative solution.
Code Snippet
// recursive method to find & update
// grandChildrens' array's elements matching "id"
const findAndUpdate = (needle, hayStack) => (
// iterate over key-value pairs of object
Object.entries(hayStack)
.forEach(([k, v]) => {
// if value is an array
if (v && Array.isArray(v)) {
// if the array corresponds to "grandChildrens"
if (k === 'grandChildrens') {
// update elements which match "id"
v = v.map(
({ id, ...rest }) => {
if (id === needle.id) {
return ({
id, ...rest, ...needle
})
};
return ({ id, ...rest })
}
);
// mutate "hayStack" with updated element
hayStack[k] = v;
} else {
// array is not "grandChildren"
// simply iterate over each object in array
v.forEach(
obj => findAndUpdate(needle, obj)
);
}
};
})
);
// method to generate a new object
// with merged information
const generateMergedObject = (ob, ar) => {
// first deep-clone the parameter "ob"
// this prevents "ob" from being changed
const resObj = structuredClone(ob);
// for each elt in "ar" array
// find and update "grandChildrens" array
// based on matching "id"
ar.forEach(obj => {
findAndUpdate(obj, resObj);
});
// return the result object
return resObj;
};
// the main object which will remain unmodified
const parentObj = {
"initiateLevel": true,
"parent": [{
"name": "level1",
"childrens": [{
"group": "Level-group",
"grandChildrens": [{
"id": 21,
"technology": "sp1",
"path": "l2"
},
{
"id": 22,
"technology": "sp2",
"path": "l2"
}
]
}]
},
{
"name": "level2",
"childrens": [{
"group": "Level-group-2",
"grandChildrens": [{
"id": 121,
"technology": "sp12",
"path": "l4"
},
{
"id": 122,
"technology": "sp22",
"path": "l4"
}
]
}]
}
]
};
// the info that will be used to update a copy of the parentObj
const updatesArr = [{
"id": 22,
"reason": "reason 2",
"phase": "phase 2",
"reviewer": "by user 2",
"date": "date 2"
},
{
"id": 21,
"reason": "reason 1",
"phase": "phase 1",
"reviewer": "by user 1",
"date": "date 1"
}
];
// invoke the method to create the new object
// and display the original object that remains as-is
console.log(
'generate new object with updated info...',
generateMergedObject(parentObj, updatesArr),
'\n\n original object remains unmodified: ',
parentObj
);
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added to the snippet above.
CodePudding user response:
Use Array.prototype.map
instead, forEach
gets no returns
let newobj = [
{
id: 22,
reason: 'reason 2',
phase: 'phase 2',
reviewer: 'by user 2',
date: 'date 2'
},
{
id: 21,
reason: 'reason 1',
phase: 'phase 1',
reviewer: 'by user 1',
date: 'date 1'
}
];
let arr1 = {
initiateLevel: true,
parent: [
{
name: 'level1',
childrens: [
{
group: 'Level-group',
grandChildrens: [
{
id: 21,
technology: 'sp1',
path: 'l2',
reason: 'reason 1',
phase: 'phase 1',
reviewer: 'by user 1',
date: 'date 1'
},
{
id: 22,
technology: 'sp2',
path: 'l2',
reason: 'reason 2',
phase: 'phase 2',
reviewer: 'by user 2',
date: 'date 2'
}
]
}
]
},
{
name: 'level2',
childrens: [
{
group: 'Level-group-2',
grandChildrens: [
{
id: 121,
technology: 'sp12',
path: 'l4'
},
{
id: 122,
technology: 'sp22',
path: 'l4'
}
]
}
]
}
]
};
const merge = (y, z) => {
const parent = y.parent.map((element) => {
return {
...element,
childrens: element.childrens.map((x) => {
return {
...x,
grandChildrens: x.grandChildrens.map((test) => {
return { ...test, ...z.find((reviewItem) => reviewItem.id === test.id) };
})
};
})
}
});
return { ...y, parent };
};
const arr2 = merge(arr1, newobj);
console.log(arr2);