javascript 动态设置嵌套对象的属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18936915/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-27 13:44:40  来源:igfitidea点击:

Dynamically set property of nested object

javascriptecmascript-5

提问by John B.

I have an object that could be any number of levels deep and could have any existing properties. For example:

我有一个对象,它可以是任意数量的深度,并且可以具有任何现有属性。例如:

var obj = {
    db: {
        mongodb: {
            host: 'localhost'
        }
    }
};

On that I would like to set (or overwrite) properties like so:

对此,我想设置(或覆盖)如下属性:

set('db.mongodb.user', 'root');
// or:
set('foo.bar', 'baz');

Where the property string can have any depth, and the value can be any type/thing.
Objects and arrays as values don't need to be merged, should the property key already exist.

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

Previous example would produce following object:

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

var obj = {
    db: {
        mongodb: {
            host: 'localhost',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

How can I realize such a function?

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

回答by bpmason1

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

此函数使用您指定的参数,应添加/更新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');

回答by aheuermann

Lodash has a _.set()method.

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

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

回答by Philll_t

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

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

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

如果对象不存在,将 set recursively 标志设置为 true 将设置对象。

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

will result in this:

将导致:

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

回答by Hem? Vidal

We can use a recursion function:

我们可以使用递归函数:

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

It's more simple!

更简单!

回答by ron4ex

ES6 has a pretty cool way to do this too using Computed Property Nameand Rest Parameter.

ES6 使用Computed Property NameRest Parameter也有一个非常酷的方法来做到这一点。

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

const updatedObj = {
  ...obj,
  levelOne: {
    ...obj.levelOne,
    levelTwo: {
      ...obj.levelOne.levelTwo,
      levelThree: "I am now updated!"
    }
  }
}

If levelThreeis a dynamic property i.e. to set any of the property in levelTwo, you can use [propertyName]: "I am now updated!"where propertyNameholds the name of the property in levelTwo.

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

回答by Bruno Joaquim

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

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

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

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

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.

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

回答by webjay

Inspired by @bpmason1's answer:

受到@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;
}

Example:

例子:

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

回答by brafdlog

Lodash has a method called updatethat does exactly what you need.

Lodash 有一个叫做update的方法,它完全符合你的需要。

This method receives the following parameters:

此方法接收以下参数:

  1. The object to update
  2. The path of the property to update (the property can be deeply nested)
  3. A function that returns the value to update (given the original value as a parameter)
  1. 要更新的对象
  2. 要更新的属性的路径(属性可以深度嵌套)
  3. 返回要更新的值的函数(给定原始值作为参数)

In your example it would look like this:

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

_.update(obj, 'db.mongodb.user', function(originalValue) {
  return 'root'
})

回答by Chiffie

I created gistfor setting and getting obj values by string based on correct answer. You can download it or use it as npm/yarn package.

我创建了用于根据正确答案按字符串设置和获取 obj 值的要点。您可以下载它或将其用作 npm/yarn 包。

// yarn add gist:5ceba1081bbf0162b98860b34a511a92
// npm install gist:5ceba1081bbf0162b98860b34a511a92
export const DeepObject = {
  set: setDeep,
  get: getDeep
};

// https://stackoverflow.com/a/6491621
function getDeep(obj: Object, path: string) {
  path = path.replace(/\[(\w+)\]/g, '.'); // convert indexes to properties
  path = path.replace(/^\./, '');           // strip a leading dot
  const a = path.split('.');
  for (let i = 0, l = a.length; i < l; ++i) {
    const n = a[i];
    if (n in obj) {
      obj = obj[n];
    } else {
      return;
    }
  }

  return obj;
}

// https://stackoverflow.com/a/18937118
function setDeep(obj: Object, path: string, value: any) {
  let schema = obj;  // a moving reference to internal objects within obj
  const pList = path.split('.');
  const len = pList.length;
  for (let i = 0; i < len - 1; i++) {
    const elem = pList[i];
    if (!schema[elem]) {
      schema[elem] = {};
    }
    schema = schema[elem];
  }

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

// Usage
// import {DeepObject} from 'somePath'
//
// const obj = {
//   a: 4,
//   b: {
//     c: {
//       d: 2
//     }
//   }
// };
//
// DeepObject.set(obj, 'b.c.d', 10); // sets obj.b.c.d to 10
// console.log(DeepObject.get(obj, 'b.c.d')); // returns 10

回答by aggregate1166877

If you only need to change deeper nested objects, then another method could be to reference the object. As JS objects are handled by their references, you can create a reference to an object you have string-key access to.

如果您只需要更改更深层次的嵌套对象,那么另一种方法可能是引用该对象。由于 JS 对象是由它们的引用处理的,因此您可以创建对具有字符串键访问权限的对象的引用。

Example:

例子:

// The object we want to modify:
var obj = {
    db: {
        mongodb: {
            host: 'localhost',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};

var key1 = 'mongodb';
var key2 = 'host';

var myRef = obj.db[key1]; //this creates a reference to obj.db['mongodb']

myRef[key2] = 'my new string';

// The object now looks like:
var obj = {
    db: {
        mongodb: {
            host: 'my new string',
            user: 'root'
        }
    },
    foo: {
        bar: baz
    }
};