Imposta dynamicmente la proprietà dell’object nidificato

Ho un object che potrebbe avere un numero qualsiasi di livelli profondo e potrebbe avere qualsiasi proprietà esistente. Per esempio:

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

Su questo vorrei impostare (o sovrascrivere) proprietà in questo modo:

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

Dove la stringa di proprietà può avere una profondità e il valore può essere qualsiasi tipo / cosa.
Oggetti e matrici come valori non devono essere uniti, se la chiave di proprietà esiste già.

L’esempio precedente produrrebbe l’object seguente:

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

Come posso realizzare una tale funzione?

Questa funzione, utilizzando gli argomenti specificati, dovrebbe aggiungere / aggiornare i dati nel contenitore obj . Nota che devi tenere traccia di quali elementi in obj schema sono contenitori e quali sono valori (stringhe, int, ecc.) Altrimenti inizierai a lanciare eccezioni.

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

Lodash ha un metodo _.set () .

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

Ispirato alla risposta di @ 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; } 

Esempio:

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

Lodash ha un metodo chiamato aggiornamento che fa esattamente ciò di cui hai bisogno.

Questo metodo riceve i seguenti parametri:

  1. L’object da aggiornare
  2. Il percorso della proprietà da aggiornare (la proprietà può essere profondamente annidata)
  3. Una funzione che restituisce il valore da aggiornare (dato il valore originale come parametro)

Nel tuo esempio sembrerebbe questo:

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

Un po ‘tardi ma ecco una risposta non-libreria, più semplice:

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

Questa funzione che ho fatto può fare esattamente quello che ti serve e un po ‘di più.

diciamo che vogliamo cambiare il valore di destinazione che è profondamente annidato in questo object:

 let myObj = { level1: { level2: { target: 1 } } } 

Quindi chiameremmo la nostra funzione in questo modo:

 setDeep(myObj, ["level1", "level2", "target1"], 3); 

risulterà in:

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

Impostando il flag ricorsivamente su true, gli oggetti verranno impostati se non esistono.

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

risulterà in questo:

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

Se hai solo bisogno di modificare oggetti nidificati più profondi, un altro metodo potrebbe essere quello di fare riferimento all’object. Poiché gli oggetti JS sono gestiti dai loro riferimenti, puoi creare un riferimento a un object a cui hai accesso tramite chiave di stringa.

Esempio:

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

Un altro approccio consiste nell’utilizzare la ricorsione per scavare attraverso l’object:

 (function(root){ function NestedSetterAndGetter(){ function setValueByArray(obj, parts, value){ if(!parts){ throw 'No parts array passed in'; } if(parts.length === 0){ throw 'parts should never have a length of 0'; } if(parts.length === 1){ obj[parts[0]] = value; } else { var next = parts.shift(); if(!obj[next]){ obj[next] = {}; } setValueByArray(obj[next], parts, value); } } function getValueByArray(obj, parts, value){ if(!parts) { return null; } if(parts.length === 1){ return obj[parts[0]]; } else { var next = parts.shift(); if(!obj[next]){ return null; } return getValueByArray(obj[next], parts, value); } } this.set = function(obj, path, value) { setValueByArray(obj, path.split('.'), value); }; this.get = function(obj, path){ return getValueByArray(obj, path.split('.')); }; } root.NestedSetterAndGetter = NestedSetterAndGetter; })(this); var setter = new this.NestedSetterAndGetter(); var o = {}; setter.set(o, 'abc', 'apple'); console.log(o); //=> { a: { b: { c: 'apple'}}} var z = { a: { b: { c: { d: 'test' } } } }; setter.set(z, 'abc', {dd: 'zzz'}); console.log(JSON.stringify(z)); //=> {"a":{"b":{"c":{"dd":"zzz"}}}} console.log(JSON.stringify(setter.get(z, 'abc'))); //=> {"dd":"zzz"} console.log(JSON.stringify(setter.get(z, 'a.b'))); //=> {"c":{"dd":"zzz"}} 

ES6 ha un ottimo modo per farlo anche usando il Nome proprietà calcolato e il parametro di rest .

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

Se levelThree è una proprietà dynamic, vale a dire impostare una proprietà in levelTwo , è ansible utilizzare [propertyName]: "I am now updated!" dove propertyName contiene il nome della proprietà in levelTwo .

Scrivo solo una piccola funzione utilizzando la ricorsione ES6 + per raggiungere l’objective.

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

L’ho usato molto per reactjs allo stato di aggiornamento, ha funzionato abbastanza bene per me.

Possiamo usare una funzione di ricorsione:

 /** * 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, path.length), value) } 

È più semplice!