I have a nested object like this
let obj = {
id: "XXX",
children: [
{
id: "YYY",
children: [],
path: ["XXX", "YYY"],
type: "text"
},
{
id: "ZZZ",
children: [],
path: ["XXX", "ZZZ"],
type: "button"
}
],
path: ["XXX"],
type: "row"
};
I need to loop through all the children and update the path by appending an array of strings which is is
const newPath = ["A", "B", "C"];
let newObj = cloneDeepWith(obj, (node) => {
if (node.id) {
node = { ...node, path: [...newPath, ...node.path] };
console.log("updated : ", node)
}
});
So final output of update object should be this
let obj = {
id: "XXX",
children: [
{
id: "YYY",
children: [],
path: ["A", "B", "C", "XXX", "YYY"],
type: "text"
},
{
id: "ZZZ",
children: [],
path: ["A", "B", "C", "XXX", "ZZZ"],
type: "button"
}
],
path: ["A", "B", "C", "XXX"],
type: "row"
};
I'm trying to do it with cloneDeepWith function in lodash like this
let updatedObj = cloneDeepWith(obj, (node) => {
if (node.id) {
node = { ...node, path: [...newPath, ...node.path] };
console.log("updated : ", node)
}
});
In every console.log it prints the corrected updated node (in this case 3 with parent node) , but it doesn't return in updatedObj
How can I achieve this ?
I'm not sure how to do this is lodash, but doing in plain JS is pretty easy. Your recursion tag is the key..
eg.
let obj = {
id: "XXX",
children: [
{
id: "YYY",
children: [],
path: ["XXX", "YYY"],
type: "text"
},
{
id: "ZZZ",
children: [],
path: ["XXX", "ZZZ"],
type: "button"
}
],
path: ["XXX"],
type: "row"
};
function addABC(obj) {
if (obj.path) obj.path = ['A','B','C',...obj.path];
if (obj.children) for (const s of obj.children) addABC(s);
}
addABC(obj);
console.log(obj);
You could do this with Lodash using cloneDeepWith method but in this case it will also modify original data.
let obj = {
id: "XXX",
children: [{
id: "YYY",
children: [],
path: ["XXX", "YYY"],
type: "text"
},
{
id: "ZZZ",
children: [],
path: ["XXX", "ZZZ"],
type: "button"
}
],
path: ["XXX"],
type: "row"
};
const newPath = ["A", "B", "C"];
const clone = _.cloneDeepWith(obj, value => {
if (_.isArray(value.path)) {
value.path = [...newPath, ...value.path]
}
})
console.log(clone)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
An immutable approach without a library could look like this:
const prependPaths = (prefix) => ({path, children, ...rest}) => ({
...rest,
path: [...prefix, ...path],
children: children .map (prependPaths (prefix))
})
const obj = {id: "XXX", children: [{id: "YYY", children: [], path: ["XXX", "YYY"], type: "text"}, {id: "ZZZ", children: [], path: ["XXX", "ZZZ"], type: "button"}], path: ["XXX"], type: "row"}
console .log (prependPaths (['A', 'B', 'C']) (obj))
.as-console-wrapper {max-height: 100% !important; top: 0}
This is a straightforward recursion, where we map over the children using our function, preconfigured with the prefix parameter (here, ['A', 'B', 'C'].)
But notice that your objects' path nodes carry some redundant information that could be derived from the hierarchy of ids, and if you have a raw form that doesn't include these path nodes, you could still achieve the same effect with about the same effort:
const addPaths = (prefix = []) => ({id, children, ...rest}) => ({
id,
...rest,
path: [...prefix, id],
children: children .map (addPaths ([...prefix, id]))
})
// No `path` nodes here.
const obj2 = {id: "XXX", children: [{id: "YYY", children: [], type: "text"}, {id: "ZZZ", children: [], type: "button"}], type: "row"}
// But the output includes the correct full paths.
console .log (addPaths (['A', 'B', 'C']) (obj2))
.as-console-wrapper {max-height: 100% !important; top: 0}
The chief difference is that the recursive calls take an updated version of the prefix, adding the id of the current object.