1

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!

1
  • Sounds like you need deep copy of your default object and then override it's values? Commented Jan 8, 2023 at 20:43

3 Answers 3

0

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)

Sign up to request clarification or add additional context in comments.

1 Comment

FYI there is no check if a key from newVal exists in defaults, so it might change structure of the final object, which in some cases not desirable.
0

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.

1 Comment

Interesting, I've not heard of Lodash! I think I'll just use a custom function from another answer for my purposes because I don't plan on using any other functions from Lodash and so don't want to add a dependency, but I will keep it in mind for future projects! Thank you!
0

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;
}

1 Comment

Thank you! This is what I was looking for. I think Kalomano's answer is slightly preferred because it iterates over the newVals not defaults and so has the potential to run more quickly. Either way, thank you for this answer!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.