Dynamically set property of nested object

A bit late but here's a non-library, simpler answer:

/**
 * Dynamically sets a deeply nested value in an object.
 * Optionally "bores" a path to it if its undefined.
 * @function
 * @param {!object} obj  - The object which contains the value you want to change/set.
 * @param {!array} path  - The array representation of path to the value you want to change/set.
 * @param {!mixed} value - The value you want to set it to.
 * @param {boolean} setrecursively - If true, will set value of non-existing path as well.
 */
function setDeep(obj, path, value, setrecursively = false) {
    path.reduce((a, b, level) => {
        if (setrecursively && typeof a[b] === "undefined" && level !== path.length){
            a[b] = {};
            return a[b];
        }

        if (level === path.length){
            a[b] = value;
            return value;
        } 
        return a[b];
    }, obj);
}

This function I made can do exactly what you need and a little more.

lets say we want to change the target value that is deeply nested in this object:

let myObj = {
    level1: {
        level2: {
           target: 1
       }
    }
}

So we would call our function like so:

setDeep(myObj, ["level1", "level2", "target1"], 3);

will result in:

myObj = { level1: { level2: { target: 3 } } }

Setting the set recursively flag to true will set objects if they don't exist.

setDeep(myObj, ["new", "path", "target"], 3, true);

will result in this:

obj = myObj = {
    new: {
         path: {
             target: 3
         }
    },
    level1: {
        level2: {
           target: 3
       }
    }
}

This function, using the arguments you specified, should add/update the data in the obj container. Note that you need to keep track of which elements in obj schema are containers and which are values (strings, ints, etc.) otherwise you will start throwing exceptions.

obj = {};  // global object

function set(path, value) {
    var schema = obj;  // a moving reference to internal objects within obj
    var pList = path.split('.');
    var len = pList.length;
    for(var i = 0; i < len-1; i++) {
        var elem = pList[i];
        if( !schema[elem] ) schema[elem] = {}
        schema = schema[elem];
    }

    schema[pList[len-1]] = value;
}

set('mongo.db.user', 'root');

I just write a small function using ES6 + recursion to achieve the goal.

updateObjProp = (obj, value, propPath) => {
    const [head, ...rest] = propPath.split('.');

    !rest.length
        ? obj[head] = value
        : this.updateObjProp(obj[head], value, rest.join('.'));
}

const user = {profile: {name: 'foo'}};
updateObjProp(user, 'fooChanged', 'profile.name');

I used it a lot on react to update state, it worked pretty well for me.


Lodash has a _.set() method.

_.set(obj, 'db.mongodb.user', 'root');
_.set(obj, 'foo.bar', 'baz');