I'm working with a JavaScript object that gives the default values for a library I am working with
const defaults = {
alpha: alpha_val,
beta: {
a: {
i: beta_a_i_val,
ii: beta_a_ii_val,
},
c: beta_c_val,
},
gamma: gamma_val,
};
So far, I've been overriding the defaults in the following way
const override = {
...defaults,
beta: {
...defaults.beta,
a: {
...defaults.beta.a,
i: beta_a_i_newval,
},
},
gamma: gamma_newval,
};
While this works, the true defaults
object is quite large and so overrides of many values becomes tedious and ugly.
I'm wondering if there is a way to write a function to do this sort of "deep spread" automatically?
So, creating the override
object above, for example, would look something like this:
const newvals = {
beta: {
a: {
i: beta_a_i_newval,
},
},
gamma: gamma_newval,
};
const overrides = someFunction(defaults, newVals)
It seems like this would be a common requirement, but I've been having trouble finding a way to actually do this. I'm very new to JavaScript, so any advice is greatly appreciated!
CodePudding user response:
You can use the lodash _.merge function for that:
const myFunc(options_){
const options = {}
const defaultOptions = {}
_.merge(options, defaultOptions)
// ... the rest of your myFunc code
}
From the docs:
it recursively merges own and inherited enumerable string keyed properties of source objects into the destination object. Source properties that resolve to undefined are skipped if a destination value exists. Array and plain object properties are merged recursively. Other objects and value types are overridden by assignment. Source objects are applied from left to right. Subsequent sources overwrite property assignments of previous sources.
generally, _.merge
is the way I merge defaults and user options in the arguments in every JavaScript module I build.
I almost never write a project without using this method.
except that I have made a wrapper for it, which returns a new object instead of mutating the original one, and I've used _.mergeWith to only merge POJOs and not arrays. because this merges the arrays and not only the objects.
You can do the same if you wanted to.
CodePudding user response:
What you are looking for is a recursive function that iterates an object and calls itself for each child object:
const defaults = {
alpha: "alpha_val",
beta: {
a: {
i: "beta_a_i_val",
ii: "beta_a_ii_val",
iii: "blah",
},
c: "beta_c_val",
},
d: null,
gamma: "gamma_val",
};
const newvals = {
beta: {
a: {
i: "beta_a_i_newval",
iii: null,
},
},
d: "blah",
gamma: "gamma_newval",
};
const result = cloneObj(defaults, newvals);
//make sure none of the original objects are affected
result.beta.a.test = "ok";
console.log("defaults", defaults);
console.log("newvals", newvals);
console.log("result", result);
function cloneObj(def, obj)
{
const ret = {};
//iterate through the default object
for(let key in def)
{
// get new value
let val = obj[key];
if (val === undefined)
val = def[key]; //fallback to default value
//if it's an object and not a NULL call itself
if (val && val instanceof Object)
val = cloneObj(def[key], val);
ret[key] = val;
}
return ret;
}
CodePudding user response:
Yes, you can write a function that takes in the defaults object and the newvals object, and returns a new object that combines the two. One way to do this would be to use a recursive function, where you loop through the keys in the newvals object and perform a "deep spread" similar to what you did before.
Here is an example of how you could write the someFunction function:
function someFunction(defaults, newvals) {
const result = {...defaults};
for (const key of Object.keys(newvals)) {
if (typeof newvals[key] === 'object' && newvals[key] !== null) {
result[key] = someFunction(defaults[key], newvals[key]);
} else {
result[key] = newvals[key];
}
}
return result;
}
This function works by creating a new object called result that is a shallow copy of the defaults object. It then loops through the keys in the newvals object and checks if the value is an object. If it is, it calls the someFunction function recursively to merge the two objects. If the value is not an object, it simply assigns the new value to the key in the result object.
You can then use the someFunction function like this:
const overrides = someFunction(defaults, newVals);
function someFunction(defaults, newvals) {
const result = {...defaults};
for (const key of Object.keys(newvals)) {
if (typeof newvals[key] === 'object' && newvals[key] !== null) {
result[key] = someFunction(defaults[key], newvals[key]);
} else {
result[key] = newvals[key];
}
}
return result;
}
const newvals = {
beta: {
a: {
i: "beta_a_i_newval",
},
},
gamma: "gamma_newval",
};
const defaults = {
alpha: "alpha_val",
beta: {
a: {
i: "beta_a_i_val",
ii: "beta_a_ii_val",
},
c: "beta_c_val",
},
gamma: "gamma_val",
};
override = someFunction(defaults,newvals);
console.log(override)