How to set object property (of object property of..) given its string name in JavaScript?

JavascriptStringPropertiesNested

Javascript Problem Overview


Suppose we are only given

var obj = {};
var propName = "foo.bar.foobar";

How can we set the property obj.foo.bar.foobar to a certain value (say "hello world")? So I want to achieve this, while we only have the property name in a string:

obj.foo.bar.foobar = "hello world";

Javascript Solutions


Solution 1 - Javascript

function assign(obj, prop, value) {
    if (typeof prop === "string")
        prop = prop.split(".");
    
    if (prop.length > 1) {
        var e = prop.shift();
        assign(obj[e] =
                 Object.prototype.toString.call(obj[e]) === "[object Object]"
                 ? obj[e]
                 : {},
               prop,
               value);
    } else
        obj[prop[0]] = value;
}

var obj = {},
    propName = "foo.bar.foobar";

assign(obj, propName, "Value");

Solution 2 - Javascript

Since this question appears to be answered by incorrect answers, I'll just refer to the correct answer from a similar question

function setDeepValue(obj, value, path) {
	if (typeof path === "string") {
		var path = path.split('.');
	}

	if(path.length > 1){
		var p=path.shift();
		if(obj[p]==null || typeof obj[p]!== 'object'){
			 obj[p] = {};
		}
		setDeepValue(obj[p], value, path);
	}else{
		obj[path[0]] = value;
	}
}

Use:

var obj = {};
setDeepValue(obj, 'Hello World', 'foo.bar.foobar');

Solution 3 - Javascript

I know it's an old one, but I see only custom functions in answers.
If you don't mind using a library, look at lodash _.set and _.get function.

Solution 4 - Javascript

edit: I've created a jsPerf.com testcase to compare the accepted answer with my version. Turns out that my version is faster, especially when you go very deep.

http://jsfiddle.net/9YMm8/

var nestedObjectAssignmentFor = function(obj, propString, value) {
    var propNames = propString.split('.'),
        propLength = propNames.length-1,
        tmpObj = obj;
    
    for (var i = 0; i <= propLength ; i++) {
        tmpObj = tmpObj[propNames[i]] = i !== propLength ?  {} : value;  
    }
    return obj;
}
        
var obj = nestedObjectAssignment({},"foo.bar.foobar","hello world");

Solution 5 - Javascript

All solutions overid any of the original data when setting so I have tweaked with the following, made it into a single object too:

 var obj = {}
 nestObject.set(obj, "a.b", "foo"); 
 nestObject.get(obj, "a.b"); // returns foo     
 
 var nestedObject = {
     set: function(obj, propString, value) {
         var propNames = propString.split('.'),
             propLength = propNames.length-1,
             tmpObj = obj;
         for (var i = 0; i <= propLength ; i++) {
             if (i === propLength){
                 if(tmpObj[propNames[i]]){
                     tmpObj[propNames[i]] = value;
                 }else{
                     tmpObj[propNames[i]] = value;
                 }
             }else{
                 if(tmpObj[propNames[i]]){
                     tmpObj = tmpObj[propNames[i]];
                 }else{
                     tmpObj = tmpObj[propNames[i]] = {};
                 }
             }
         }
         return obj;
     },
     get: function(obj, propString){
         var propNames = propString.split('.'),
             propLength = propNames.length-1,
             tmpObj = obj;
         for (var i = 0; i <= propLength ; i++) {
             if(tmpObj[propNames[i]]){
                 tmpObj = tmpObj[propNames[i]];
             }else{
                 break;
             }
         }
         return tmpObj;
     }
 };

Can also change functions to be an Oject.prototype method changing obj param to this:

Object.prototype = { setNested = function(){ ... }, getNested = function(){ ... } } 

{}.setNested('a.c','foo') 

Solution 6 - Javascript

Here is a get and set function i just compiled from a couple of threads + some custom code.

It will also create keys that don't exist on set.

function setValue(object, path, value) {
    var a = path.split('.');
    var o = object;
    for (var i = 0; i < a.length - 1; i++) {
        var n = a[i];
        if (n in o) {
            o = o[n];
        } else {
            o[n] = {};
            o = o[n];
        }
    }
    o[a[a.length - 1]] = value;
}

function getValue(object, path) {
    var o = object;
    path = path.replace(/\[(\w+)\]/g, '.$1');
    path = path.replace(/^\./, '');
    var a = path.split('.');
    while (a.length) {
        var n = a.shift();
        if (n in o) {
            o = o[n];
        } else {
            return;
        }
    }
    return o;
}

Solution 7 - Javascript

Here's one that returns the updated object

function deepUpdate(value, path, tree, branch = tree) {
  const last = path.length === 1;
  branch[path[0]] = last ? value : branch[path[0]];
  return last ? tree : deepUpdate(value, path.slice(1), tree, branch[path[0]]);
}

const path = 'cat.dog';
const updated = deepUpdate('a', path.split('.'), {cat: {dog: null}})
// => { cat: {dog: 'a'} }

Solution 8 - Javascript

Here is a simple function to do that using reference.

    function setValueByPath (obj, path, value) {
        var ref = obj;

        path.split('.').forEach(function (key, index, arr) {
            ref = ref[key] = index === arr.length - 1 ? value : {};
        });

        return obj;
    }

Solution 9 - Javascript

You could split the path and make a check if the following element exist. If not assign an object to the new property.

Return then the value of the property.

At the end assign the value.

function setValue(object, path, value) { var fullPath = path.split('.'), way = fullPath.slice(), last = way.pop();

    way.reduce(function (r, a) {
        return r[a] = r[a] || {};
    }, object)[last] = value;
}

var object = {},
    propName = 'foo.bar.foobar',
    value = 'hello world';

setValue(object, propName, value);
console.log(object);

Solution 10 - Javascript

A very straightforward one.

This implementation should be very performant. It avoids recursions, and function calls, while maintaining simplicity.

/**
 * Set the value of a deep property, creating new objects as necessary.
 * @param {Object} obj The object to set the value on.
 * @param {String|String[]} path The property to set.
 * @param {*} value The value to set.
 * @return {Object} The object at the end of the path.
 * @author github.com/victornpb
 * @see https://stackoverflow.com/a/46060952/938822
 * @example
 * setDeep(obj, 'foo.bar.baz', 'quux');
 */
function setDeep(obj, path, value) {
    const props = typeof path === 'string' ? path.split('.') : path;
    for (var i = 0, n = props.length - 1; i < n; ++i) {
        obj = obj[props[i]] = obj[props[i]] || {};
    }
    obj[props[i]] = value;
    return obj;
}
  
  

/*********************** EXAMPLE ***********************/

const obj = {
    hello : 'world',
};

setDeep(obj, 'root', true);
setDeep(obj, 'foo.bar.baz', 1);
setDeep(obj, ['foo','quux'], '😉');

console.log(obj);
// ⬇︎ Click "Run" below to see output

Solution 11 - Javascript

I was looking for an answer that does not overwrite existing values and was easily readable and was able to come up with this. Leaving this here in case it helps others with the same needs

function setValueAtObjectPath(obj, pathString, newValue) {
  // create an array (pathComponents) of the period-separated path components from pathString
  var pathComponents = pathString.split('.');
  // create a object (tmpObj) that references the memory of obj
  var tmpObj = obj;

  for (var i = 0; i < pathComponents.length; i++) {
    // if not on the last path component, then set the tmpObj as the value at this pathComponent
    if (i !== pathComponents.length-1) {
      // set tmpObj[pathComponents[i]] equal to an object of it's own value
      tmpObj[pathComponents[i]] = {...tmpObj[pathComponents[i]]}
      // set tmpObj to reference tmpObj[pathComponents[i]]
      tmpObj = tmpObj[pathComponents[i]]
    // else (IS the last path component), then set the value at this pathComponent equal to newValue 
    } else {
      // set tmpObj[pathComponents[i]] equal to newValue
      tmpObj[pathComponents[i]] = newValue
    }
  }
  // return your object
  return obj
}

Solution 12 - Javascript

Same as Rbar's answers, very useful when you're working with redux reducers. I use lodash clone instead of spread operator to support arrays too:

export function cloneAndPatch(obj, path, newValue, separator='.') {
    let stack = Array.isArray(path) ? path : path.split(separator);
    let newObj = _.clone(obj);

    obj = newObj;

    while (stack.length > 1) {
        let property = stack.shift();
        let sub = _.clone(obj[property]);

        obj[property] = sub;
        obj = sub;
    }

    obj[stack.shift()] = newValue;

    return newObj;
}

Solution 13 - Javascript

Object.getPath = function(o, s) {
	s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
	s = s.replace(/^\./, '');           // strip a leading dot
	var a = s.split('.');
	for (var i = 0, n = a.length; i < n; ++i) {
		var k = a[i];
		if (k in o) {
			o = o[k];
		} else {
			return;
		}
	}
	return o;
};

Object.setPath = function(o, p, v) {
	var a = p.split('.');
	var o = o;
	for (var i = 0; i < a.length - 1; i++) {
		if (a[i].indexOf('[') === -1) {
			var n = a[i];
			if (n in o) {
				o = o[n];
			} else {
				o[n] = {};
				o = o[n];
			}
		} else {
			// Not totaly optimised
			var ix = a[i].match(/\[.*?\]/g)[0];
			var n = a[i].replace(ix, '');
			o = o[n][ix.substr(1,ix.length-2)]
		}
	}

	if (a[a.length - 1].indexOf('[') === -1) {
		o[a[a.length - 1]] = v;
	} else {
		var ix = a[a.length - 1].match(/\[.*?\]/g)[0];
		var n = a[a.length - 1].replace(ix, '');
		o[n][ix.substr(1,ix.length-2)] = v;
	}
};

Solution 14 - Javascript

Here's a simple method that uses a scoped Object that recursively set's the correct prop by path.

function setObjectValueByPath(pathScope, value, obj) {
  const pathStrings = pathScope.split('/');
  obj[pathStrings[0]] = pathStrings.length > 1 ?
    setObjectValueByPath(
      pathStrings.splice(1, pathStrings.length).join('/'),
      value,
      obj[pathStrings[0]]
    ) :
    value;
  return obj;
}

Solution 15 - Javascript

How about a simple and short one?

Object.assign(this.origin, { [propName]: value })

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionchtenbView Question on Stackoverflow
Solution 1 - JavascriptVisioNView Answer on Stackoverflow
Solution 2 - JavascriptCerbrusView Answer on Stackoverflow
Solution 3 - JavascriptMichał FrączkiewiczView Answer on Stackoverflow
Solution 4 - JavascriptStephan Bönnemann-WalentaView Answer on Stackoverflow
Solution 5 - JavascriptLabithiotisView Answer on Stackoverflow
Solution 6 - JavascriptDieter GribnitzView Answer on Stackoverflow
Solution 7 - JavascriptDaniel LizikView Answer on Stackoverflow
Solution 8 - JavascriptThiago KrogerView Answer on Stackoverflow
Solution 9 - JavascriptNina ScholzView Answer on Stackoverflow
Solution 10 - JavascriptVitim.usView Answer on Stackoverflow
Solution 11 - JavascriptRbarView Answer on Stackoverflow
Solution 12 - JavascriptmakerooView Answer on Stackoverflow
Solution 13 - Javascriptip.View Answer on Stackoverflow
Solution 14 - JavascriptMax SandelinView Answer on Stackoverflow
Solution 15 - JavascriptAnton NikitsiukView Answer on Stackoverflow