What my function does:
Safely sets object properties with dot notation strings in JavaScript.
That is to say, it allows setting a nested property on an object using a string such as "a.b.c".
For example: set(obj, "a.b.c", value)
would be equivalent to a.b.c = value
Some notes on intended behavior
- When setting a property at a given path, if any portion of that path doesn't exist, it should be created
- When creating a path portion, if the next key in the path is NOT an integer, the current portion should be created as an object
- When creating a path portion, if the next key in the path is an integer, the current portion should be created as an array
Why?
I'd like to be able to set deeply nested properties on an object in an environment where I can't be sure that all the properties in the path will always exist and I don't want to deal with the logic of checking for and setting each key manually in more than one place. This allows me to quickly set the needed value without caring if the keys were present previously or not.
/**
* Set the value for the given object for the given path
* where the path can be a nested key represented with dot notation
*
* @param {object} obj The object on which to set the given value
* @param {string} path The dot notation path to the nested property where the value should be set
* @param {mixed} value The value that should be set
* @return {mixed}
*
*/
function set(obj, path, value) {
// protect against being something unexpected
obj = typeof obj === 'object' ? obj : {};
// split the path into and array if its not one already
var keys = Array.isArray(path) ? path : path.split('.');
// keep up with our current place in the object
// starting at the root object and drilling down
var curStep = obj;
// loop over the path parts one at a time
// but, dont iterate the last part,
for (var i = 0; i < keys.length - 1; i++) {
// get the current path part
var key = keys[i];
// if nothing exists for this key, make it an empty object or array
if (!curStep[key] && !Object.prototype.hasOwnProperty.call(curStep, key)){
// get the next key in the path, if its numeric, make this property an empty array
// otherwise, make it an empty object
var nextKey = keys[i+1];
var useArray = /^\+?(0|[1-9]\d*)$/.test(nextKey);
curStep[key] = useArray ? [] : {};
}
// update curStep to point to the new level
curStep = curStep[key];
}
// set the final key to our value
var finalStep = keys[keys.length - 1];
curStep[finalStep] = value;
};
/** Usage **/
console.log('setting non existant a.b.c to "some value":');
var test = {};
set(test, 'a.b.c', 'some value');
console.log(test);
console.log('updating a.b.c to "some new value":');
set(test, 'a.b.c', 'some new value');
console.log(test);
console.log('setting non existant a.b.0 to "some value":');
var test = {};
set(test, 'a.b.0', 'some value');
console.log(test);
console.log('updating a.b.0 to "some new value":');
set(test, 'a.b.0', 'some new value');
console.log(test);