Javascript: como criar dinamicamente objects nesteds usando nomes de objects dados por um array

Espero que alguém possa me ajudar com este Javascript.

Eu tenho um object chamado “Configurações” e gostaria de escrever uma function que adiciona novas configurações para esse object.

O nome e o valor da nova configuração são fornecidos como strings. A string que fornece o nome da configuração é então dividida pelos sublinhados em uma matriz. A nova configuração deve ser adicionada ao object “Configurações” existente, criando novos objects nesteds com os nomes dados por cada parte da matriz, exceto a última parte, que deve ser uma cadeia que fornece o valor da configuração. Eu deveria então poder me referir ao cenário e, por exemplo, alertar seu valor. Eu posso fazer isso de uma forma estática como esta …

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

… a parte que cria os objects nesteds está fazendo isso …

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

No entanto, como o número de partes que compõem o nome da configuração pode variar, por exemplo, um newSettingName poderia ser “Modules_Floorplan_Image_Src”, eu gostaria de fazer isso dinamicamente usando uma function como …

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

Alguém pode me ajudar a descobrir como fazer isso dinamicamente?

Eu presumo que tem que haver um for … loop lá para iterar através da matriz, mas eu não fui capaz de descobrir uma maneira de criar os objects nesteds.

Se você chegou até aqui, muito obrigado por ter tempo para ler, mesmo que você não possa ajudar.

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

Uso:

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

Coloque em uma function, curta e rápida (sem 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. 

Ele ignora partes já existentes da hierarquia. Útil se você não tiver certeza se a hierarquia já foi criada.

Ou:

Uma versão mais sofisticada na qual você pode atribuir diretamente o valor ao último object na hierarquia e pode encadear chamadas de function porque ele retorna o último 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 

Nota: se a sua hierarquia precisa ser construída a partir de valores que não sejam objects padrão (ou seja, não {} ), veja também a resposta do TimDog abaixo.

Edit: usa loops regulares em vez de for...in loops. É mais seguro nos casos em que uma biblioteca modifica o protótipo Array.

Outra solução recursiva:

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

Exemplo de uso:

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

Minha solução ES2015. Mantém os valores existentes.

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

Exemplo:

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

Aqui está um simples ajuste para a resposta do jlgrall que permite definir valores distintos em cada elemento na hierarquia aninhada:

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

Espero que ajude.

Aqui está uma solução funcional para criar dinamicamente objects nesteds.

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

Exemplo:

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

Usando ES6 é encurtar. Defina seu caminho em uma matriz. Primeiro, você tem que inverter o array, para começar a preencher o object.

 let obj = ['a','b','c'] // {a:{b:{c:{}}} obj.reverse(); const nestedObject = obj.reduce((prev, current) => ( {[current]:{...prev}} ), {}); 

tente usar a function recursiva:

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

Eu acho que isso é mais curto:

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

Descobri que a resposta do @jlgrall foi ótima, mas após simplificá-la, não funcionou no Chrome. Aqui está o meu fixo, se alguém quiser uma versão lite:

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

Espero que isso seja útil e relevante. Desculpe, acabei de quebrar esse exemplo …

Você pode definir seus próprios methods de object; Também estou usando sublinhado por brevidade:

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

Uso da amostra:

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

Aprecio que esta pergunta é mega antiga! Mas depois de encontrar uma necessidade de fazer algo parecido com isso no nó, fiz um módulo e o publiquei no npm. Nestob

 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 

Um snippet para aqueles que precisam criar um object nested com suporte de chaves de matriz para definir um valor para o final do caminho. Path é a string como: modal.product.action.review.2.write.survey.data . Baseado na versão 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]] || {}; } } }; 

Definir dados nesteds:

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

Obter dados nesteds:

 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 

Eval é provavelmente um exagero, mas o resultado é simples de visualizar, sem loops nesteds ou recursivos.

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

Basicamente, você está progressivamente escrevendo uma string "obj['one']= {}", "obj['one']['two']"= {} e avaliando-a;

Tente isto: https://github.com/silkyland/object-to-formdata

 var obj2fd = require('obj2fd/es5').default var fd = obj2fd({ a:1, b:[ {c: 3}, {d: 4} ] }) 

Resultado:

 fd = [ a => 1, b => [ c => 3, d => 4 ] ] 

Eu amo este caminho imutável ES6 para definir um certo valor no campo nested:

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

Em seguida, use-o para criar seu object de destino.

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