【问题标题】:How to set object property (of object property of..) given its string name in JavaScript?如何在 JavaScript 中给定其字符串名称设置对象属性(对象属性的..)?
【发布时间】:2020-04-21 11:59:53
【问题描述】:

假设我们只得到了

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

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

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

【问题讨论】:

标签: javascript string properties nested


【解决方案1】:
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");

【讨论】:

  • 是的,当路径还不存在时,这个似乎也可以工作。
  • 为什么要检查 typeof 属性?无论如何,您继续功能流程。
  • @StephanBönnemann,如果它不是一个字符串,当我们需要用obj[prop[0]] = value;设置属性时,我们正在迭代中
  • @StephanBönnemann 不确定你的意思。添加它是为了使解决方案具有通用性,以便我们可以将数组或字符串作为prop 传递。
  • 这不适用于数组。模板[0].item[0].format.color
【解决方案2】:

由于这个问题的答案似乎是错误的答案,我只参考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;
    }
}

用途:

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

【讨论】:

  • 嗯。你的答案看起来像我的一模一样:)
  • 看起来我在typeof 中遗漏了=。但坦率地说,typeof 是确保您不会尝试拆分对象的唯一方法,这是我遇到的事情之一。剩下的就是简单的递归。
  • 如果我用foo.bar.foobarfoo.bar2.foobar2 调用它两次怎么办?
  • 在这种情况下,它确实在设置foo.bar2.foobar2 时重置了obj.foo。编辑了代码。糟糕,没有看到您建议的修改,正在修复中。
  • @Cerbrus 还有一件事。如果为 obj.foo.bar 分配了一个值,然后我尝试 setDeepValue foo.bar.foobar 它失败并出现错误(除非该值是布尔值false)。可能最安全的方法是测试 obj[p] 是否是一个对象。如果不是对象,请将其替换为 {}。由于 null 是一个对象,所以正确的测试应该是 if(obj[p]!=null && typeof obj[p]=== 'object')
【解决方案3】:

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • This appears to be slightly slower in Chrome 比建议的递归函数。 但是,它在 IE / FF 中明显更快。 (不过,Chrome 的迭代次数最多 / 秒,V8 是执行 JavaScript 的野兽)
    • @Cerbrus 似乎依赖于浏览器,我自己创建了测试用例jsperf.com/nested-object-assignment
    • 代码性能测试101:不要每次迭代都初始化你的函数
    • 仅适用于空的第一个参数对象,否则会覆盖原始对象数据。
    【解决方案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') 
    

    【讨论】:

      【解决方案6】:

      这是我刚刚从几个线程 + 一些自定义代码编译的 get 和 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;
      }
      

      【讨论】:

      • 我喜欢这个,因为它也有数组索引,例如“财产[3]”。但是,要让 setValue 像这样工作,我们还必须添加 "path = path.replace(/[(\w+)]/g, '.$1');"到 setValue 方法。
      【解决方案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;
          }
      

      【讨论】:

        【解决方案8】:

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

        然后返回属性的值。

        最后赋值。

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

        【讨论】:

          【解决方案9】:

          这是一个返回更新后的对象

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

          【讨论】:

            【解决方案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

            【讨论】:

              【解决方案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
              }
              

              【讨论】:

                【解决方案12】:

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

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

                【讨论】:

                  【解决方案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;
                      }
                  };
                  

                  【讨论】:

                    【解决方案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;
                    }
                    

                    【讨论】:

                      【解决方案15】:

                      来个简单又短的怎么样?

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

                      【讨论】:

                        猜你喜欢
                        • 2015-07-25
                        • 2021-12-27
                        相关资源
                        最近更新 更多