javascript - objetos - Establecer dinĂ¡micamente la propiedad del objeto anidado
recorrer array de objetos javascript (12)
Tengo un objeto que podría tener varios niveles de profundidad y podría tener cualquier propiedad existente. Por ejemplo:
var obj = {
db: {
mongodb: {
host: ''localhost''
}
}
};
En eso me gustaría establecer (o sobrescribir) las propiedades de esta manera:
set(''db.mongodb.user'', ''root'');
// or:
set(''foo.bar'', ''baz'');
Donde la cadena de propiedad puede tener cualquier profundidad, y el valor puede ser cualquier tipo / cosa.
Los objetos y matrices como valores no necesitan combinarse, 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?
ES6 también tiene una forma genial de hacer esto usando Nombre de propiedad computada y Parámetro de reposo .
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 propiedades en levelTwo
, puede usar [propertyName]: "I am now updated!"
donde propertyName
contiene el nombre de la propiedad en levelTwo
.
Esta función, usando los argumentos que especificó, debe agregar / actualizar los datos en el contenedor obj
. Tenga en cuenta que debe hacer un seguimiento de qué elementos del esquema obj
son contenedores y cuáles son valores (cadenas, entradas, etc.), de lo contrario, comenzará a lanzar 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'');
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'' } }
Lodash tiene un método _.set() .
_.set(obj, ''db.mongodb.user'', ''root'');
_.set(obj, ''foo.bar'', ''baz'');
Lodash tiene un método llamado update que hace exactamente lo que necesita.
Este método recibe los siguientes parámetros:
- El objeto para actualizar
- La ruta de la propiedad para actualizar (la propiedad puede estar profundamente anidada)
- Una función que devuelve el valor para actualizar (dado el valor original como parámetro)
En tu ejemplo, se vería así:
_.update(obj, ''db.mongodb.user'', function(originalValue) {
return ''root''
})
Otro enfoque es usar la recursión para cavar a través del 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"}}
Podemos usar una función de recursión:
/** * 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) }
¡Es más simple!
Si desea una función que requiera que existan propiedades anteriores, entonces podría usar algo como esto, también devolvería un indicador que indique 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; }); }
Si solo necesita cambiar los objetos anidados más profundos, entonces 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 tenga 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
}
};
Solo escribo una pequeña función usando recursión ES6 + para lograr el objetivo.
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'');
Lo usé mucho en reaccionar al estado de actualización, funcionó bastante bien para mí.
Un poco tarde, pero aquí hay una respuesta no más simple de la 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) {
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);
}
Esta función que hice puede hacer exactamente lo que necesita 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
}
}
}
Así que llamaríamos a nuestra función así:
setDeep(myObj, ["level1", "level2", "target1"], 3);
resultará en:
myObj = {level1: {level2: {target: 3}}}
Establecer el indicador recursivamente conjunto en verdadero establecerá objetos si no existen.
setDeep(myObj, ["new", "path", "target"], 3);
dará como resultado esto:
obj = myObj = {
new: {
path: {
target: 3
}
},
level1: {
level2: {
target: 3
}
}
}
Creé gist para establecer y obtener valores obj por cadena en función de la respuesta correcta. Puede descargarlo o usarlo como paquete npm / hilo.
/**
* 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)
}