Tengo un objeto que podría tener cualquier cantidad de niveles de profundidad y podría tener propiedades existentes. Por ejemplo:
var obj = {
db: {
mongodb: {
host: 'localhost'
}
}
};
En eso, me gustaría establecer (o sobrescribir) propiedades así:
set('db.mongodb.user', 'root');
// or:
set('foo.bar', 'baz');
Donde la cadena de propiedad puede tener cualquier profundidad y el valor puede ser de cualquier tipo / cosa.
Los objetos y matrices como valores no necesitan fusionarse, si la clave de propiedad ya existe.
El ejemplo anterior produciría el siguiente objeto:
var obj = {
db: {
mongodb: {
host: 'localhost',
user: 'root'
}
},
foo: {
bar: baz
}
};
¿Cómo puedo realizar tal función?
javascript
ecmascript-5
John B.
fuente
fuente
set('foo', 'bar'); set('foo.baz', 'qux');
, dóndefoo
primero se mantiene un yString
luego se convierte en unObject
? ¿Qué pasa con'bar'
?set()
método y simplementeobj.db.mongodb.user = 'root';
tiene exactamente lo que parece querer?bar
se sobrescribe con elObject
. @adeneo y @rmertins De hecho :) Pero desafortunadamente tengo que envolver otra lógica. @Robert Levy Encontré ese y conseguí que el acceso funcionara, pero configurarlo parece mucho más complicado ...Respuestas:
Esta función, usando los argumentos que especificó, debería agregar / actualizar los datos en el
obj
contenedor. Tenga en cuenta que debe realizar un seguimiento de qué elementos delobj
esquema son contenedores y cuáles son valores (cadenas, entradas, etc.); de lo contrario, comenzará a generar excepciones.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');
fuente
var schema = obj
lugar de solo enobj
todas partes?schema
es un puntero que se mueve por la ruta conschema = schema[elem]
. Entonces, después del ciclo for,schema[pList[len - 1]]
apunta a mongo.db.user enobj
.cloneDeep
función lodash para esto.Lodash tiene un método _.set () .
_.set(obj, 'db.mongodb.user', 'root'); _.set(obj, 'foo.bar', 'baz');
fuente
Un poco tarde, pero aquí hay una respuesta más simple que no es de biblioteca:
/** * 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); }
Esta función que hice puede hacer exactamente lo que necesitas y un poco más.
digamos que queremos cambiar el valor objetivo que está profundamente anidado en este objeto:
let myObj = { level1: { level2: { target: 1 } } }
Entonces llamaríamos a nuestra función así:
setDeep(myObj, ["level1", "level2", "target1"], 3);
resultará en:
myObj = {level1: {level2: {target: 3}}}
Establecer el indicador de conjunto recursivamente en verdadero establecerá objetos si no existen.
setDeep(myObj, ["new", "path", "target"], 3, true);
resultará en esto:
obj = myObj = { new: { path: { target: 3 } }, level1: { level2: { target: 3 } } }
fuente
level
, utilicéreduce
el tercer argumento.level
debe ser +1 opath.length
-1Podemos usar una función de recursividad:
/** * 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) }
¡Es más simple!
fuente
obj[path[0]] = value;
porquepath
siempre es de tipostring[]
, incluso cuando solo queda 1 cadena.obj[['a']] = 'new value'
. Verifique el código: jsfiddle.net/upsdne03Solo escribo una pequeña función usando ES6 + recursividad para lograr el objetivo.
updateObjProp = (obj, value, propPath) => { const [head, ...rest] = propPath.split('.'); !rest.length ? obj[head] = value : this.updateObjProp(obj[head], value, rest.join('.')); } const user = {profile: {name: 'foo'}}; updateObjProp(user, 'fooChanged', 'profile.name');
Lo usé mucho para reaccionar para actualizar el estado, funcionó bastante bien para mí.
fuente
this.updateStateProp(obj[head], value, rest);
debería serthis.updateStateProp(obj[head], value, rest.join());
ES6 también tiene una manera genial de hacer esto usando el nombre de propiedad calculado y el parámetro de descanso .
const obj = { levelOne: { levelTwo: { levelThree: "Set this one!" } } } const updatedObj = { ...obj, levelOne: { ...obj.levelOne, levelTwo: { ...obj.levelOne.levelTwo, levelThree: "I am now updated!" } } }
Si
levelThree
es una propiedad dinámica, es decir, para establecer cualquiera de las propiedadeslevelTwo
, puede usar[propertyName]: "I am now updated!"
wherepropertyName
contiene el nombre de la propiedadlevelTwo
.fuente
Lodash tiene un método llamado actualización que hace exactamente lo que necesita.
Este método recibe los siguientes parámetros:
En su ejemplo, se vería así:
_.update(obj, 'db.mongodb.user', function(originalValue) { return 'root' })
fuente
Inspirado por la respuesta de @ 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; }
Ejemplo:
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' } }
fuente
Creé lo esencial para establecer y obtener valores obj por cadena según la respuesta correcta. Puede descargarlo o usarlo como paquete 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, '.$1'); // 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
fuente
Si solo necesita cambiar objetos anidados más profundos, otro método podría ser hacer referencia al objeto. Como los objetos JS son manejados por sus referencias, puede crear una referencia a un objeto al que tiene acceso de clave de cadena.
Ejemplo:
// 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 } };
fuente
Otro enfoque es usar la recursividad para excavar en el objeto:
(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, 'a.b.c', 'apple'); console.log(o); //=> { a: { b: { c: 'apple'}}} var z = { a: { b: { c: { d: 'test' } } } }; setter.set(z, 'a.b.c', {dd: 'zzz'}); console.log(JSON.stringify(z)); //=> {"a":{"b":{"c":{"dd":"zzz"}}}} console.log(JSON.stringify(setter.get(z, 'a.b.c'))); //=> {"dd":"zzz"} console.log(JSON.stringify(setter.get(z, 'a.b'))); //=> {"c":{"dd":"zzz"}}
fuente
Necesitaba lograr lo mismo, pero en Node.js ... Entonces, encontré este bonito módulo: https://www.npmjs.com/package/nested-property
Ejemplo:
var mod = require("nested-property"); var obj = { a: { b: { c: { d: 5 } } } }; console.log(mod.get(obj, "a.b.c.d")); mod.set(obj, "a.b.c.d", 6); console.log(mod.get(obj, "a.b.c.d"));
fuente
Se me ocurrió mi propia solución usando es6 puro y recursividad que no mute el objeto original.
const setNestedProp = (obj = {}, [first, ...rest] , value) => ({ ...obj, [first]: rest.length ? setNestedProp(obj[first], rest, value) : value }); const result = setNestedProp({}, ["first", "second", "a"], "foo"); const result2 = setNestedProp(result, ["first", "second", "b"], "bar"); console.log(result); console.log(result2);
fuente
Si desea que exista una función que requiera que existan propiedades anteriores, entonces podría usar algo como esto, también devolvería una bandera que indica si logró encontrar y establecer la propiedad anidada.
function set(obj, path, value) { var parts = (path || '').split('.'); // using 'every' so we can return a flag stating whether we managed to set the value. return parts.every((p, i) => { if (!obj) return false; // cancel early as we havent found a nested prop. if (i === parts.length - 1){ // we're at the final part of the path. obj[parts[i]] = value; }else{ obj = obj[parts[i]]; // overwrite the functions reference of the object with the nested one. } return true; }); }
fuente
Inspirado en ClojureScript
assoc-in
( https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L5280 ), usando recursividad:/** * Associate value (v) in object/array (m) at key/index (k). * If m is falsy, use new object. * Returns the updated object/array. */ function assoc(m, k, v) { m = (m || {}); m[k] = v; return m; } /** * Associate value (v) in nested object/array (m) using sequence of keys (ks) * to identify the path to the nested key/index. * If one of the values in the nested object/array doesn't exist, it adds * a new object. */ function assoc_in(m={}, [k, ...ks], v) { return ks.length ? assoc(m, k, assoc_in(m[k], ks, v)) : assoc(m, k, v); } /** * Associate value (v) in nested object/array (m) using key string notation (s) * (e.g. "k1.k2"). */ function set(m, s, v) { ks = s.split("."); return assoc_in(m, ks, v); }
Nota:
Con la implementación proporcionada,
assoc_in({"a": 1}, ["a", "b"], 2)
devoluciones
{"a": 1}
Preferiría que arrojara un error en este caso. Si lo desea, puede agregar un registro
assoc
para verificarm
que sea un objeto o una matriz y, de lo contrario, arrojar un error.fuente
Intenté escribir este método de conjunto en breve , ¡puede ayudar a alguien!
function set(obj, key, value) { let keys = key.split('.'); if(keys.length<2){ obj[key] = value; return obj; } let lastKey = keys.pop(); let fun = `obj.${keys.join('.')} = {${lastKey}: '${value}'};`; return new Function(fun)(); } var obj = { "hello": { "world": "test" } }; set(obj, "hello.world", 'test updated'); console.log(obj); set(obj, "hello.world.again", 'hello again'); console.log(obj); set(obj, "hello.world.again.onece_again", 'hello once again'); console.log(obj);
fuente
JQuery tiene un método de extensión:
https://api.jquery.com/jquery.extend/
simplemente pase las sobrescrituras como un objeto y fusionará los dos.
fuente