动态设置嵌套对象的属性

Joh*_* B. 59 javascript ecmascript-5

我有一个对象,可以是任意数量的级别,可以有任何现有的属性.例如:

var obj = {
    db: {
        mongodb: {
            host: 'localhost'
        }
    }
};
Run Code Online (Sandbox Code Playgroud)

在那我想设置(或覆盖)属性,如下所示:

set('db.mongodb.user', 'root');
// or:
set('foo.bar', 'baz');
Run Code Online (Sandbox Code Playgroud)

属性字符串可以有任何深度,值可以是任何类型/东西.
如果属性键已存在,则不需要合并作为值的对象和数组.

上一个示例将生成以下对象:

var obj = {
    db: {
        mongodb: {
            host: 'localhost',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};
Run Code Online (Sandbox Code Playgroud)

我怎么才能实现这样的功能呢?

bpm*_*on1 77

此函数使用您指定的参数,应添加/更新obj容器中的数据.请注意,您需要跟踪obj模式中的哪些元素是容器,哪些是值(字符串,整数等),否则您将开始抛出异常.

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');
Run Code Online (Sandbox Code Playgroud)

  • @ sman591`schema`是一个指针,它通过`schema = schema [elem]`向下移动。因此,在for循环之后,“ schema [pList [len-1]]”指向“ obj”中的mongo.db.user。 (3认同)
  • @ bpmason1您能解释一下为什么在各处都使用`var schema = obj`而不是`obj`吗? (2认同)

ahe*_*ann 51

Lodash有一个_.set()方法.

_.set(obj, 'db.mongodb.user', 'root');
_.set(obj, 'foo.bar', 'baz');
Run Code Online (Sandbox Code Playgroud)

  • 请注意,当键的一部分包含“foo.bar.350350”等数字时,这将无法按预期工作。相反,它将创建 350350 个空元素! (4认同)
  • 它也可以用来设置 key 的值吗?如果是的话你可以分享一个例子吗?谢谢 (2认同)

Bru*_*uim 18

我只是用ES6+递归写了一个小函数来达到目的。

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');
Run Code Online (Sandbox Code Playgroud)

我在反应更新状态时经常使用它,它对我来说效果很好。

  • 这很方便,我必须在 proPath 上放置一个 toString() 才能使其与嵌套属性一起使用,但之后效果很好。const [head, ...rest] = propPath.toString().split('.'); (2认同)
  • @user738048 @Bruno-Joaquim 行 `this.updateStateProp(obj[head], value, rest);` 应该是 `this.updateStateProp(obj[head], value, rest.join());` (2认同)

Phi*_*l_t 11

有点晚了,但这里是一个非库,更简单的答案:

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

    let level = 0;

    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;
        } else {
            return a[b];
        }
    }, obj);
}
Run Code Online (Sandbox Code Playgroud)

我所做的这个功能可以完全满足您的需求和更多功能.

假设我们想要更改深度嵌套在此对象中的目标值:

let myObj = {
    level1: {
        level2: {
           target: 1
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我们会像这样调用我们的函数:

setDeep(myObj, ["level1", "level2", "target1"], 3);
Run Code Online (Sandbox Code Playgroud)

将导致:

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

将set recursively flag设置为true将设置对象(如果它们不存在).

setDeep(myObj, ["new", "path", "target"], 3);
Run Code Online (Sandbox Code Playgroud)

会导致这个:

obj = myObj = {
    new: {
         path: {
             target: 3
         }
    },
    level1: {
        level2: {
           target: 3
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我相信 `level` 需要是 +1 或 `path.length` -1 (6认同)
  • 这非常有帮助,谢谢! (2认同)
  • 不执行归约时不应使用归约。 (2认同)

Hen*_*ons 11

我想出了我自己的解决方案,使用纯 es6 和递归,不会改变原始对象。

const setNestedProp = (obj = {}, [first, ...rest] , value) => ({
  ...obj,
  [first]: rest.length
    ? setNestedProp(obj[first], rest, value)
    : value
});

const result = setNestedProp({}, ["first", "second", "a"], 
"foo");
const result2 = setNestedProp(result, ["first", "second", "b"], "bar");

console.log(result);
console.log(result2);
Run Code Online (Sandbox Code Playgroud)


web*_*jay 7

灵感来自@ bpmason1的答案:

function leaf(obj, path, value) {
  const pList = path.split('.');
  const key = pList.pop();
  const pointer = pList.reduce((accumulator, currentValue) => {
    if (accumulator[currentValue] === undefined) accumulator[currentValue] = {};
    return accumulator[currentValue];
  }, obj);
  pointer[key] = value;
  return obj;
}
Run Code Online (Sandbox Code Playgroud)

例:

const obj = {
  boats: {
    m1: 'lady blue'
  }
};
leaf(obj, 'boats.m1', 'lady blue II');
leaf(obj, 'boats.m2', 'lady bird');
console.log(obj); // { boats: { m1: 'lady blue II', m2: 'lady bird' } }
Run Code Online (Sandbox Code Playgroud)


bra*_*log 6

Lodash有一个名为update的方法,可以完全满足您的需求.

此方法接收以下参数:

  1. 要更新的对象
  2. 要更新的属性的路径(属性可以深度嵌套)
  3. 返回要更新的值的函数(将原始值作为参数)

在您的示例中,它看起来像这样:

_.update(obj, 'db.mongodb.user', function(originalValue) {
  return 'root'
})
Run Code Online (Sandbox Code Playgroud)


Hem*_*dal 5

我们可以使用递归函数:

/**
 * Sets a value of nested key string descriptor inside a Object.
 * It changes the passed object.
 * Ex:
 *    let obj = {a: {b:{c:'initial'}}}
 *    setNestedKey(obj, ['a', 'b', 'c'], 'changed-value')
 *    assert(obj === {a: {b:{c:'changed-value'}}})
 *
 * @param {[Object]} obj   Object to set the nested key
 * @param {[Array]} path  An array to describe the path(Ex: ['a', 'b', 'c'])
 * @param {[Object]} value Any value
 */
export const setNestedKey = (obj, path, value) => {
  if (path.length === 1) {
    obj[path] = value
    return
  }
  return setNestedKey(obj[path[0]], path.slice(1), value)
}
Run Code Online (Sandbox Code Playgroud)

更简单!

  • 看起来不错!只需检查obj参数以确保其不为假,如果链中的任何道具都不存在,则会抛出错误。 (2认同)
  • 您可以只使用path.slice(1); (2认同)

ron*_*4ex 5

ES6也有一个很酷的方法,即使用计算属性名称Rest参数来执行此操作

const obj = {
  levelOne: {
    levelTwo: {
      levelThree: "Set this one!"
    }
  }
}

const updatedObj = {
  ...obj,
  levelOne: {
    ...obj.levelOne,
    levelTwo: {
      ...obj.levelOne.levelTwo,
      levelThree: "I am now updated!"
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

如果levelThree是动态属性,即要在中设置任何属性levelTwo,则可以使用[propertyName]: "I am now updated!"where来propertyName保存属性的名称levelTwo