Javascript:如何使用数组给定的对象名称动态创建嵌套对象
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5484673/
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
Javascript: how to dynamically create nested objects using object names given by an array
提问by David
I hope someone can help me with this Javascript.
我希望有人可以帮助我使用这个 Javascript。
I have an Object called "Settings" and I would like to write a function that adds new settings to that object.
我有一个名为“设置”的对象,我想编写一个向该对象添加新设置的函数。
The new setting's name and value are provided as strings. The string giving the setting's name is then split by the underscores into an array. The new setting should get added to the existing "Settings" object by creating new nested objects with the names given by each part of the array, except the last part which should be a string giving the setting's value. I should then be able to refer to the setting and e.g. alert its value. I can do this in a static way like this...
新设置的名称和值以字符串形式提供。给出设置名称的字符串然后被下划线分割成一个数组。新的设置应该通过创建新的嵌套对象添加到现有的“设置”对象中,该对象的名称由数组的每个部分给出,除了最后一部分应该是给出设置值的字符串。然后我应该能够参考设置,例如提醒它的值。我可以像这样以静态方式做到这一点......
var Settings = {};
var newSettingName = "Modules_Video_Plugin";
var newSettingValue = "JWPlayer";
var newSettingNameArray = newSettingName.split("_");
Settings[newSettingNameArray[0]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]][newSettingNameArray[2]] = newSettingValue;
alert(Settings.Modules.Mediaplayers.Video.Plugin);
... the part that creates the nested objects is doing this ...
...创建嵌套对象的部分正在执行此操作...
Settings["Modules"] = {};
Settings["Modules"]["Video"] = {};
Settings["Modules"]["Video"]["Plugin"] = "JWPlayer";
However, as the number of parts that make up the setting name can vary, e.g. a newSettingName could be "Modules_Floorplan_Image_Src", I'd like to do this dynamically using a function such as...
但是,由于构成设置名称的部分数量可能会有所不同,例如 newSettingName 可能是“Modules_Floorplan_Image_Src”,我想使用诸如...
createSetting (newSettingNameArray, newSettingValue);
function createSetting(setting, value) {
// code to create new setting goes here
}
Can anyone help me work out how to do this dynamically?
谁能帮我弄清楚如何动态地做到这一点?
I presume there has to be a for...loop in there to itterate through the array, but I haven't been able to work out a way to create the nested objects.
我认为必须有一个 for... 循环来遍历数组,但我无法找到创建嵌套对象的方法。
If you've got this far thanks very much for taking the time to read even if you can't help.
如果你已经读到这里,非常感谢你花时间阅读,即使你无能为力。
回答by jlgrall
Put in a function, short and fast (no recursion).
放入一个函数,简短而快速(无递归)。
var createNestedObject = function( base, names ) {
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
};
// Usage:
createNestedObject( window, ["shapes", "triangle", "points"] );
// Now window.shapes.triangle.points is an empty object, ready to be used.
It skips already existing parts of the hierarchy. Useful if you are not sure whether the hierarchy was already created.
它跳过层次结构中已经存在的部分。如果您不确定层次结构是否已创建,则很有用。
Or:
或者:
A fancier version where you can directly assign the value to the last object in the hierarchy, and you can chain function calls because it returns the last object.
一个更好的版本,您可以直接将值分配给层次结构中的最后一个对象,并且您可以链接函数调用,因为它返回最后一个对象。
// Function: createNestedObject( base, names[, value] )
// base: the object on which to create the hierarchy
// names: an array of strings contaning the names of the objects
// value (optional): if given, will be the last object in the hierarchy
// Returns: the last object in the hierarchy
var createNestedObject = function( base, names, value ) {
// If a value is given, remove the last name and keep it for later:
var lastName = arguments.length === 3 ? names.pop() : false;
// Walk the hierarchy, creating new objects where needed.
// If the lastName was removed, then the last object is not set yet:
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
// If a value was given, set it to the last name:
if( lastName ) base = base[ lastName ] = value;
// Return the last object in the hierarchy:
return base;
};
// Usages:
createNestedObject( window, ["shapes", "circle"] );
// Now window.shapes.circle is an empty object, ready to be used.
var obj = {}; // Works with any object other that window too
createNestedObject( obj, ["shapes", "rectangle", "width"], 300 );
// Now we have: obj.shapes.rectangle.width === 300
createNestedObject( obj, "shapes.rectangle.height".split('.'), 400 );
// Now we have: obj.shapes.rectangle.height === 400
Note: if your hierarchy needs to be built from values other that standard objects (ie. not {}
), see also TimDog's answer below.
注意:如果您的层次结构需要从标准对象(即 not {}
)以外的值构建,另请参阅下面的 TimDog 的回答。
Edit: uses regular loops instead of for...in
loops. It's safer in cases where a library modifies the Array prototype.
编辑:使用常规循环而不是for...in
循环。在库修改 Array 原型的情况下更安全。
回答by kennytm
function assign(obj, keyPath, value) {
lastKeyIndex = keyPath.length-1;
for (var i = 0; i < lastKeyIndex; ++ i) {
key = keyPath[i];
if (!(key in obj)){
obj[key] = {}
}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
Usage:
用法:
var settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');
回答by Laurens
My ES2015 solution. Keeps existing values.
我的 ES2015 解决方案。保留现有值。
const set = (obj, path, val) => {
const keys = path.split('.');
const lastKey = keys.pop();
const lastObj = keys.reduce((obj, key) =>
obj[key] = obj[key] || {},
obj);
lastObj[lastKey] = val;
};
Example:
例子:
const obj = {'a': {'prop': {'that': 'exists'}}};
set(obj, 'a.very.deep.prop', 'value');
console.log(JSON.stringify(obj));
// {"a":{"prop":{"that":"exists"},"very":{"deep":{"prop":"value"}}}}
回答by Diego Molina
Using ES6 is shorten. Set your path into an array. first, you have to reverse the array, to start filling the object.
使用 ES6 会缩短。将您的路径设置为一个数组。首先,您必须反转数组以开始填充对象。
let obj = ['a','b','c'] // {a:{b:{c:{}}}
obj.reverse();
const nestedObject = obj.reduce((prev, current) => (
{[current]:{...prev}}
), {});
回答by nerfologist
Another recursive solution:
另一个递归解决方案:
var nest = function(obj, keys, v) {
if (keys.length === 1) {
obj[keys[0]] = v;
} else {
var key = keys.shift();
obj[key] = nest(typeof obj[key] === 'undefined' ? {} : obj[key], keys, v);
}
return obj;
};
Example usage:
用法示例:
var dog = {bark: {sound: 'bark!'}};
nest(dog, ['bark', 'loudness'], 66);
nest(dog, ['woff', 'sound'], 'woff!');
console.log(dog); // {bark: {loudness: 66, sound: "bark!"}, woff: {sound: "woff!"}}
回答by TimDog
Here is a simple tweak to jlgrall's answer that allows setting distinctvalues on each element in the nested hierarchy:
这是对 jlgrall 答案的一个简单调整,它允许在嵌套层次结构中的每个元素上设置不同的值:
var createNestedObject = function( base, names, values ) {
for( var i in names ) base = base[ names[i] ] = base[ names[i] ] || (values[i] || {});
};
Hope it helps.
希望能帮助到你。
回答by Aliaksandr Sushkevich
Here is a functional solution to dynamically create nested objects.
这是动态创建嵌套对象的功能解决方案。
const nest = (path, obj) => {
const reversedPath = path.split('.').reverse();
const iter = ([head, ...tail], obj) => {
if (!head) {
return obj;
}
const newObj = {[head]: {...obj}};
return iter(tail, newObj);
}
return iter(reversedPath, obj);
}
Example:
例子:
const data = {prop: 'someData'};
const path = 'a.deep.path';
const result = nest(path, data);
console.log(JSON.stringify(result));
// {"a":{"deep":{"path":{"prop":"someData"}}}}
回答by Vadim Shvetsov
I love this ES6 immutable way to set certain value on nested field:
我喜欢这种在嵌套字段上设置特定值的 ES6 不可变方式:
const setValueToField = (fields, value) => {
const reducer = (acc, item, index, arr) => ({ [item]: index + 1 < arr.length ? acc : value });
return fields.reduceRight(reducer, {});
};
And then use it with creating your target object.
然后将其用于创建目标对象。
const targetObject = setValueToField(['one', 'two', 'three'], 'nice');
console.log(targetObject); // Output: { one: { two: { three: 'nice' } } }
回答by Chrift
Appreciate that this question is mega old! But after coming across a need to do something like this in node, I made a module and published it to npm. Nestob
感谢这个问题是超级古老的!但是在遇到需要在 node 中执行类似操作后,我创建了一个模块并将其发布到 npm。 内斯托
var nestob = require('nestob');
//Create a new nestable object - instead of the standard js object ({})
var newNested = new nestob.Nestable();
//Set nested object properties without having to create the objects first!
newNested.setNested('biscuits.oblong.marmaduke', 'cheese');
newNested.setNested(['orange', 'tartan', 'pipedream'], { poppers: 'astray', numbers: [123,456,789]});
console.log(newNested, newNested.orange.tartan.pipedream);
//{ biscuits: { oblong: { marmaduke: 'cheese' } },
orange: { tartan: { pipedream: [Object] } } } { poppers: 'astray', numbers: [ 123, 456, 789 ] }
//Get nested object properties without having to worry about whether the objects exist
//Pass in a default value to be returned if desired
console.log(newNested.getNested('generic.yoghurt.asguard', 'autodrome'));
//autodrome
//You can also pass in an array containing the object keys
console.log(newNested.getNested(['chosp', 'umbridge', 'dollar'], 'symbols'));
//symbols
//You can also use nestob to modify objects not created using nestob
var normalObj = {};
nestob.setNested(normalObj, 'running.out.of', 'words');
console.log(normalObj);
//{ running: { out: { of: 'words' } } }
console.log(nestob.getNested(normalObj, 'random.things', 'indigo'));
//indigo
console.log(nestob.getNested(normalObj, 'improbable.apricots'));
//false
回答by Vyacheslav Voronchuk
A snippet for those who need to create a nested objects with support of array keys to set a value to the end of path. Path is the string like: modal.product.action.review.2.write.survey.data
. Based on jlgrall version.
对于那些需要创建支持数组键的嵌套对象以将值设置为路径末尾的人的片段。路径是这样的字符串:modal.product.action.review.2.write.survey.data
。基于 jlgrall 版本。
var updateStateQuery = function(state, path, value) {
var names = path.split('.');
for (var i = 0, len = names.length; i < len; i++) {
if (i == (len - 1)) {
state = state[names[i]] = state[names[i]] || value;
}
else if (parseInt(names[i+1]) >= 0) {
state = state[names[i]] = state[names[i]] || [];
}
else {
state = state[names[i]] = state[names[i]] || {};
}
}
};