I am trying to overwrite an object given specific changes to that object. The problem is that there are other nested objects that get overwritten as well. How would I prevent this?
const changes = {
"b": {
"x": "new",
}
};
let newBody = {
"a": 3,
"b": {
"x": "old",
"y": "fields"
},
"c": 8
};
const expected = {
"newBody": {
"a": 3,
"b": {
"x": "new",
"y": "fields"
},
"c": 8
}
};
Here is a Unit Test example of what I am trying to do. I want to take the changes object, and basically replace b.x in newBody, but I also want to preserve the other fields like b.y, a, and C. I want to make it as dynamic as possible, so if there is another object for newBody.b.x or another value for A, I want the code to be able to notice that and adequately change that. Does anyone have an idea on what to do here?
for (let [key, value] of Object.entries(changes)) {
for(let [key1, value1] of Object.entries(value)) {
newBody[key][key1] = value1;
}
}
This is what I have so far in terms of Code. But it only takes into account the fact that it only needs to traverse through two objects to replace. If I had something like:
const changes = {
"b": {
"x": "new",
"y": {
"n": "iphone"
}
}
};
The code would not work. How do I make it as dynamic as possible to realize how many objects it needs to replace?
CodePudding user response:
Here's a function that assigns values for matching keys from a source object into a target. It does so recursively.
Lodash _merge() does something like this, probably handling many more edge cases than I've anticipated here (which is approximately none).
const changes = {
"b": {
"x": "new",
}
};
let newBody = {
"a": 3,
"b": {
"x": "old",
"y": "fields"
},
"c": 8
};
// overwrite values in target with matching keys in source
// side-effects target, also returns it
function merge(target, source) {
for (const [key, value] of Object.entries(source)) {
if (key in target) {
if (typeof value === 'object' && typeof target[key] === 'object') {
merge(target[key], value);
} else {
target[key] = value;
}
}
}
return target;
}
const r = merge(newBody, changes)
console.log(r)
CodePudding user response:
If you don't know how deep an object/array is nested you often need to make use of recursive functions (functions that call themselves)
The function I wrote below is that.
I'll try to explain how it works:
For simplicities sake we're gonna assume there is only 1 property per "layer" (the function can actually handle multiple - since we loop, but to explain the recursive part we're just gonna simplify the whole thing)
So you start by checking if the property of the changes object is itself an object or not. If it not an object, this means we are at the deepest level of this particular nesting, so we can replace the property of our actual object with the property of the changes object. If the property is however an object we need to go deeper. So what we do in this case is call the function again but pass only the property object (in our case we would pass {x: "new"}
and {x: "old", y: "fields"}
- we do this until we don't find an object as our property in which case we'll replace.
This way we can go however deep we need and replace only properties that are primitives.
const changes = {
"b": {
"x": "new",
}
};
let newBody = {
"a": 3,
"b": {
"x": "old",
"y": "fields"
},
"c": 8
};
function doTheThing(theObject, changes) {
const keys = Object.keys(changes)
keys.forEach(key => {
if (typeof changes[key] === "object" && changes[key] !== null) {
//this will also be true if changes[key] is an array
doTheThing(theObject[key], changes[key])
} else {
theObject[key] = changes[key]
}
})
return theObject
}
console.log(doTheThing(newBody, changes))
So we solved that problem with recursion (there are tons of resources about that if you need more information)