Javascript:如何使用数组给定的对象名称动态创建嵌套对象


70

我希望有人可以帮助我使用此Javascript。

我有一个名为“设置”的对象,我想编写一个向该对象添加新设置的函数。

新设置的名称和值以字符串形式提供。然后,用下划线将给出设置名称的字符串分成一个数组。通过使用数组的每个部分指定的名称创建新的嵌套对象,新的设置应该添加到现有的“设置”对象中,最后一部分除外,最后一个部分应该是给出设置值的字符串。然后,我应该能够引用该设置并例如警告其值。我可以像这样以静态方式进行此操作...

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

...创建嵌套对象的部分正在执行此操作...

Settings["Modules"] = {};
Settings["Modules"]["Video"] = {};
Settings["Modules"]["Video"]["Plugin"] = "JWPlayer";

但是,由于组成设置名称的部分数量可能会有所不同,例如newSettingName可能是“ Modules_Floorplan_Image_Src”,因此我想使用诸如...的函数动态地执行此操作。

createSetting (newSettingNameArray, newSettingValue);

function createSetting(setting, value) {
    // code to create new setting goes here
}

谁能帮我解决如何动态地做到这一点?

我猜想那里必须有一个for ...循环才能遍历整个数组,但是我一直无法找到一种创建嵌套对象的方法。

如果您已经做到了这一点,非常感谢您花时间阅读,即使您无法帮助。

Answers:


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

用法:

var settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');

2
好吧,除了我要解释的东西外,我几乎写了同样的东西newSettingName.split('_')。我看不到重复答案的意义,所以在那里。也许可以更好地解释您的答案。
基督教徒

该函数中的keyPath和value到底是什么?
Lachezar Raychev '16

太好了 我确实达到了最后的价值,因此失败了。了解,搜索并找到了这个。处于家长水平是关键,并且效果很好。
Manohar Reddy Poreddy

在上面的示例中,keyPath在此处示例的意思是,密钥是Modules.Video.Plugin,值是JWPlayer,在他们尝试构建的json中
Manohar Reddy Poreddy

97

放入一个函数,简短快速(无递归)。

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.

它跳过层次结构中已经存在的部分。如果您不确定是否已创建层次结构,则很有用。

要么:

一个更高级的版本,您可以直接将值分配给层次结构中的最后一个对象,并且可以链接函数调用,因为它返回最后一个对象。

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

注意:如果您的层次结构需要从标准对象以外的其他值(即not {})构建,请参见下面的TimDog的答案。

编辑:使用常规循环而不是for...in循环。在库修改Array原型的情况下,这样做更安全。


我希望您意识到您正在回答的问题已经超过15个月了……
Bart 2012年

没关系,问这个问题的人不太可能还在寻找答案。您的答案仍然不错(并且与其他答案不同),所以我给您一些赞许。
巴特2012年

13
实际上,这是最好的答案,因为它的行为符合预期。
2012年

@jlgrall如果我需要从另一个处理实际值分配的函数调用此函数,是否有办法返回新的键路径,而不是键的值(基数)?

13
改善现有答案永远不会太晚。
费利佩·阿拉尔孔

20

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

例:

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

11

另一个递归解决方案:

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

用法示例:

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

10

使用ES6的时间缩短了。将路径设置为数组。首先,您必须反转数组,才能开始填充对象。

let obj = ['a','b','c'] // {a:{b:{c:{}}}
obj.reverse();

const nestedObject = obj.reduce((prev, current) => (
    {[current]:{...prev}}
), {});

1
太棒了!如果nestedObject是先前定义的并且设置了{a:{b:{}}而不设置了abc,如何更改?即保留现有密钥,仅添加缺失项。那真的很有用。
temuri

6

我喜欢这种ES6不变的方法来在嵌套字段上设置某些值:

const setValueToField = (fields, value) => {
  const reducer = (acc, item, index, arr) => ({ [item]: index + 1 < arr.length ? acc : value });
  return fields.reduceRight(reducer, {});
};

然后将其用于创建目标对象。

const targetObject = setValueToField(['one', 'two', 'three'], 'nice');
console.log(targetObject); // Output: { one: { two: { three: 'nice' } } }

完善。这次真是万分感谢。
55 Cancri

ES6的出色用法
Blazes

5

这是jlgrall答案的一个简单调整,它允许在嵌套层次结构中的每个元素上设置不同的值:

var createNestedObject = function( base, names, values ) {
    for( var i in names ) base = base[ names[i] ] = base[ names[i] ] || (values[i] || {});
};

希望能帮助到你。


1
为什么这在行之有效,而不是删除现有的嵌套对象?先生,你真是个主见。
David Angel

我知道这很老,但我想我会喜欢。它起作用的原因是“基本”参数由对原始对象的引用的副本传递。因此,在每次迭代中,从右到左的基数都会成为指向当前分配属性位置的指针。
克里斯

5

这是动态创建嵌套对象的功能解决方案。

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

例:

const data = {prop: 'someData'};
const path = 'a.deep.path';
const result = nest(path, data);
console.log(JSON.stringify(result));
// {"a":{"deep":{"path":{"prop":"someData"}}}}

1

赞赏这个问题已经过时了!但是在遇到需要在节点中执行类似操作的需求后,我制作了一个模块并将其发布到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

0

尝试使用递归函数:

function createSetting(setting, value, index) {
  if (typeof index !== 'number') {
    index = 0;
  }

  if (index+1 == setting.length ) {
    settings[setting[index]] = value;
  }
  else {
    settings[setting[index]] = {};
    createSetting(setting, value, ++index);
  }
}

0

我认为这更短:

Settings = {};
newSettingName = "Modules_Floorplan_Image_Src";
newSettingValue = "JWPlayer";
newSettingNameArray = newSettingName.split("_");

a = Settings;
for (var i = 0 in newSettingNameArray) {
    var x = newSettingNameArray[i];
    a[x] = i == newSettingNameArray.length-1 ? newSettingValue : {};
    a = a[x];
}

0

我发现@jlgrall的答案很棒,但经过简化后,它在Chrome中不起作用。如果有人想要精简版,这是我的固定方法:

var callback = 'fn.item1.item2.callbackfunction',
    cb = callback.split('.'),
    baseObj = window;

function createNestedObject(base, items){
    $.each(items, function(i, v){
        base = base[v] = (base[v] || {});
    });
}

callbackFunction = createNestedObject(baseObj, cb);

console.log(callbackFunction);

我希望这是有用的和有意义的。抱歉,我已经将这个示例粉碎了……


0

您可以定义自己的Object方法;为了简洁我也使用下划线:

var _ = require('underscore');

// a fast get method for object, by specifying an address with depth
Object.prototype.pick = function(addr) {
    if (!_.isArray(addr)) return this[addr]; // if isn't array, just get normally
    var tmpo = this;
    while (i = addr.shift())
        tmpo = tmpo[i];
    return tmpo;
};
// a fast set method for object, put value at obj[addr]
Object.prototype.put = function(addr, val) {
    if (!_.isArray(addr)) this[addr] = val; // if isn't array, just set normally
    this.pick(_.initial(addr))[_.last(addr)] = val;
};

用法示例:

var obj = { 
           'foo': {
                   'bar': 0 }}

obj.pick('foo'); // returns { bar: 0 }
obj.pick(['foo','bar']); // returns 0
obj.put(['foo', 'bar'], -1) // obj becomes {'foo': {'bar': -1}}

0

那些需要创建嵌套对象并支持数组键以将值设置为路径末尾的用户的摘要。Path是像字符串: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]] || {};
        }
    }
};

0

设置嵌套数据:

function setNestedData(root, path, value) {
  var paths = path.split('.');
  var last_index = paths.length - 1;
  paths.forEach(function(key, index) {
    if (!(key in root)) root[key] = {};
    if (index==last_index) root[key] = value;
    root = root[key];
  });
  return root;
}

var obj = {'existing': 'value'};
setNestedData(obj, 'animal.fish.pet', 'derp');
setNestedData(obj, 'animal.cat.pet', 'musubi');
console.log(JSON.stringify(obj));
// {"existing":"value","animal":{"fish":{"pet":"derp"},"cat":{"pet":"musubi"}}}

获取嵌套数据:

function getNestedData(obj, path) {
  var index = function(obj, i) { return obj && obj[i]; };
  return path.split('.').reduce(index, obj);
}
getNestedData(obj, 'animal.cat.pet')
// "musubi"
getNestedData(obj, 'animal.dog.pet')
// undefined

0

评估可能是过大的,但结果很容易可视化,没有嵌套循环或递归。

 function buildDir(obj, path){
   var paths = path.split('_');
   var final = paths.pop();
   for (let i = 1; i <= paths.length; i++) {
     var key = "obj['" + paths.slice(0, i).join("']['") + "']"
     console.log(key)
     eval(`${key} = {}`)
   }
   eval(`${key} = '${final}'`)
   return obj
 }

 var newSettingName = "Modules_Video_Plugin_JWPlayer";
 var Settings = buildDir( {}, newSettingName );

基本上,您正在逐步编写一个字符串"obj['one']= {}", "obj['one']['two']"= {} 并使其逃避。



0

您可以在循环中使用lodash.set并为您创建路径:

...
const set = require('lodash.set');

const p = {};
const [type, lang, name] = f.split('.');
set(p, [lang, type, name], '');

console.log(p);
// { lang: { 'type': { 'name': '' }}}

0

这是对几个有用功能的分解,每个功能均保留现有数据。不处理数组。

  • setDeep:回答问题。对对象中的其他数据无损。
  • setDefaultDeep:相同,但仅在尚未设置的情况下设置。
  • setDefault:设置一个密钥(如果尚未设置)。与Python相同setdefault
  • setStructure:建立路径的辅助函数。

// Create a nested structure of objects along path within obj. Only overwrites the final value.
let setDeep = (obj, path, value) =>
    setStructure(obj, path.slice(0, -1))[path[path.length - 1]] = value

// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setDefaultDeep = (obj, path, value) =>
    setDefault(setStructure(obj, path.slice(0, -1)), path[path.length - 1], value)

// Set obj[key] to value if key is not in object, and return obj[key]
let setDefault = (obj, key, value) =>
    obj[key] = key in obj ? obj[key] : value;

// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setStructure = (obj, path) => 
    path.reduce((obj, segment) => setDefault(obj, segment, {}), obj);



// EXAMPLES
let temp = {};

// returns the set value, similar to assignment
console.log('temp.a.b.c.d:', 
            setDeep(temp, ['a', 'b', 'c', 'd'], 'one'))

// not destructive to 'one'
setDeep(temp, ['a', 'b', 'z'], 'two')

// does not overwrite, returns previously set value
console.log('temp.a.b.z:  ', 
            setDefaultDeep(temp, ['a', 'b', 'z'], 'unused'))

// creates new, returns current value
console.log('temp["a.1"]: ', 
            setDefault(temp, 'a.1', 'three'))

// can also be used as a getter
console.log("temp.x.y.z:  ", 
            setStructure(temp, ['x', 'y', 'z']))


console.log("final object:", temp)

我不确定为什么有人会想要字符串路径:

  1. 它们对于带句点的键是不明确的
  2. 您必须首先构建字符串
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.