如何在JavaScript中设置对象属性(对象属性...)给定其字符串名称?

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

提问人:chtenb 提问时间:12/5/2012 最后编辑:user2864740chtenb 更新时间:8/9/2022 访问量:36625

问:

假设我们只得到

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

我们如何将属性设置为某个值(比如“hello world”)? 所以我想实现这一点,而我们只有一个字符串中的属性名称:obj.foo.bar.foobar

obj.foo.bar.foobar = "hello world";
JavaScript 字符串 属性 嵌套

评论

3赞 Cerbrus 12/5/2012
stackoverflow.com/questions/6842795/ 的副本...
1赞 Ankur Marwaha 8/21/2016
这可能会帮助那些试图理解这个问题答案的人......stackoverflow.com/questions/39060905/......

答:

12赞 Cerbrus 12/5/2012 #1

由于这个问题似乎被错误的答案所回答,我将只参考类似问题的正确答案

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

用:

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

评论

3赞 VisioN 12/5/2012
嗯。你的答案看起来像我的:)
0赞 Cerbrus 12/5/2012
看起来我在 .但坦率地说,这是确保你没有试图拆分一个对象的唯一方法,这是我遇到的事情之一。其余的,就是简单的递归。=typeoftypeof
1赞 FrancescoMM 12/5/2012
如果我用 和 调用它两次怎么办?foo.bar.foobarfoo.bar2.foobar2
0赞 Cerbrus 12/5/2012
在这种情况下,它确实重置了 ,当设置 .已编辑代码。哎呀,没有看到您建议的编辑,修复。obj.foofoo.bar2.foobar2
0赞 FrancescoMM 12/5/2012
@Cerbrus还有一件事。如果 obj.foo.bar 被分配了一个值,然后我尝试设置DeepValue foo.bar.foobar,它会失败并出现错误(除非该值是布尔值)。可能最安全的方法是测试 obj[p] 是否为对象。如果不是对象,请将其替换为 {}。由于 null 是一个对象,因此正确的测试应该是falseif(obj[p]!=null && typeof obj[p]=== 'object')
92赞 VisioN 12/5/2012 #2
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");

评论

1赞 chtenb 12/5/2012
是的,当路径尚不存在时,这个似乎也有效。
0赞 Stephan Bönnemann-Walenta 12/5/2012
为什么要检查道具的类型?无论如何,您都可以继续执行函数流。
0赞 Cerbrus 12/5/2012
@StephanBönnemann,如果它不是字符串,则当我们需要使用obj[prop[0]] = value;
0赞 VisioN 12/5/2012
@StephanBönnemann 不知道你的意思。添加此内容是为了使解决方案统一,以便我们可以将数组或字符串作为 传递。prop
1赞 Demodave 8/8/2018
这不适用于数组。模板[0].item[0].format.color
5赞 Stephan Bönnemann-Walenta 12/5/2012 #3

编辑:我创建了一个 jsPerf.com 测试用例,将接受的答案与我的版本进行比较。 事实证明,我的版本更快,尤其是当你深入时。

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");

评论

0赞 Cerbrus 12/5/2012
这在 Chrome 中似乎比建议的递归函数略慢。但是,它在 IE / FF 中要快得多。(尽管如此,Chrome 的迭代次数/秒最多,V8 是 JavaScript 执行的野兽)
0赞 Stephan Bönnemann-Walenta 12/5/2012
@Cerbrus似乎这取决于浏览器,我自己创建了测试用例 jsperf.com/nested-object-assignment
1赞 Cerbrus 12/5/2012
代码性能测试 101:不要在每次迭代时都初始化函数
0赞 art 9/21/2016
仅适用于空的第一个参数对象,否则会覆盖原始对象数据。
3赞 Dieter Gribnitz 11/27/2013 #4

这是我刚刚从几个线程+一些自定义代码编译的获取和设置函数。

它还将创建片场不存在的密钥。

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

评论

1赞 Etherman 12/4/2018
我喜欢这个,因为它也做数组索引,例如“property[3]”。但是,要使 setValue 像这样工作,我们还必须将 “path = path.replace(/[(\w+)]/g, '.$1');” 添加到 setValue 方法中。
4赞 Labithiotis 3/13/2014 #5

所有解决方案在设置时都会覆盖任何原始数据,因此我调整了以下内容,也将其制作成一个对象:

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

也可以将函数更改为 Oject.prototype 方法,将 obj 参数更改为:

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

{}.setNested('a.c','foo') 
3赞 Daniel Lizik 7/22/2016 #6

下面是一个返回更新对象的对象

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'} }
3赞 Thiago Kroger 10/19/2016 #7

这是一个使用引用执行此操作的简单函数。

    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;
    }
15赞 Michał Frączkiewicz 11/24/2016 #8

我知道这是一个旧的,但我只在答案中看到自定义函数。
如果您不介意使用库,请查看 lodash _.set_.get 函数。

3赞 Nina Scholz 3/30/2017 #9

您可以拆分路径并检查以下元素是否存在。如果没有,则将对象分配给新属性。

然后返回属性的值。

最后,分配值。

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

3赞 Vitim.us 9/6/2017 #10

一个非常简单的问题。

此实现应该非常高性能。 它避免了递归和函数调用,同时保持了简单性。

/**
 * 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

评论

0赞 vsync 8/8/2022
应支持深度阵列的设置:setDeep(obj, 'foo.bar.baz[0]', 1);
0赞 Vitim.us 9/14/2022
@vsync我在数组中设置值,但如果数组不存在,则假定创建对象,并威胁索引作为键。
1赞 Rbar 10/19/2018 #11

我一直在寻找一个不会覆盖现有值且易于阅读并且能够提出这个问题的答案。把它留在这里,以防它帮助其他有同样需求的人

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
}
1赞 makeroo 1/5/2019 #12

与 Rbar 的答案相同,当您使用 redux reducers 时非常有用。我也使用 lodash clone 而不是 spread 运算符来支持数组:

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;
}
1赞 ip. 11/17/2019 #13
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;
    }
};
1赞 Max Sandelin 3/13/2020 #14

下面是一个简单的方法,它使用一个范围,该范围递归地按路径设置正确的道具。Object

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;
}
0赞 Anton Nikitsiuk 1/19/2022 #15

一个简单而简短的怎么样?

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

0赞 Tkin R. 8/9/2022 #16

您可以使用:(您可以通过在浏览器控制台上复制/粘贴来测试它)reduce

const setValueOf = (obj, value, ...path) => {
    path.reduce((o, level, idx) => {
        if(idx === path.length -1) { o[level] = value }; // on last change the value of the prop
        return o && o[level]; // return the prop
    }, obj);
};

let objExmp = {a: 'a', b: {b1: 'b1', b2: 'b2', b3: { b3_3 : 'default_value' } }};

setValueOf(objExmp, 'new_value' , 'b', 'b3', 'b3_3');
console.log('objExmp', objExmp); // prop changed to 'new_value'

你可以用'.'拆分字符串路径,并像这样传播:

setValueOf(objExmp, 'new_value' , ...'b.b3.b3_3'.split('.'));