Mam obiekt, który może mieć dowolną liczbę poziomów głębokości i może mieć dowolne istniejące właściwości. Na przykład:
var obj = {
db: {
mongodb: {
host: 'localhost'
}
}
};
Na tym chciałbym ustawić (lub nadpisać) takie właściwości:
set('db.mongodb.user', 'root');
// or:
set('foo.bar', 'baz');
Gdzie ciąg właściwości może mieć dowolną głębokość, a wartość może być dowolnym typem / rzeczą.
Obiekty i tablice jako wartości nie muszą być łączone, jeśli klucz właściwości już istnieje.
Poprzedni przykład dałby następujący obiekt:
var obj = {
db: {
mongodb: {
host: 'localhost',
user: 'root'
}
},
foo: {
bar: baz
}
};
Jak mogę zrealizować taką funkcję?
javascript
ecmascript-5
John B.
źródło
źródło
set('foo', 'bar'); set('foo.baz', 'qux');
, gdziefoo
najpierw trzyma a,String
a potem staje sięObject
? Co się dzieje'bar'
?set()
metodę i po prostuobj.db.mongodb.user = 'root';
uzyskasz dokładnie to, czego chcesz?bar
zostanie nadpisany przezObject
. @adeneo i @rmertins Owszem :) Ale niestety muszę owinąć inną logikę. @Robert Levy Znalazłem to i uzyskałem dostęp do pracy, ale ustawienie wydaje się o wiele bardziej skomplikowane ...Odpowiedzi:
Ta funkcja, używając podanych argumentów, powinna dodać / zaktualizować dane w
obj
kontenerze. Zauważ, że musisz śledzić, które elementy wobj
schemacie są kontenerami, a które wartościami (ciągi znaków, liczby wewnętrzne itp.) W przeciwnym razie zaczniesz rzucać wyjątki.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');
źródło
var schema = obj
zamiastobj
wszędzie?schema
to wskaźnik, który jest przesuwany w dół ścieżki za pomocąschema = schema[elem]
. Więc po pętli forschema[pList[len - 1]]
wskazuje na plik mongo.db.userobj
.cloneDeep
funkcji lodash .Lodash ma metodę _.set () .
_.set(obj, 'db.mongodb.user', 'root'); _.set(obj, 'foo.bar', 'baz');
źródło
Trochę późno, ale oto prostsza odpowiedź, która nie jest biblioteką:
/** * 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); }
Ta funkcja, którą stworzyłem, może zrobić dokładnie to, czego potrzebujesz i trochę więcej.
powiedzmy, że chcemy zmienić wartość docelową, która jest głęboko zagnieżdżona w tym obiekcie:
let myObj = { level1: { level2: { target: 1 } } }
Więc nazwalibyśmy naszą funkcję w ten sposób:
setDeep(myObj, ["level1", "level2", "target1"], 3);
spowoduje:
myObj = {level1: {level2: {target: 3}}}
Ustawienie rekurencyjnej flagi set na true spowoduje ustawienie obiektów, jeśli nie istnieją.
setDeep(myObj, ["new", "path", "target"], 3, true);
spowoduje to:
obj = myObj = { new: { path: { target: 3 } }, level1: { level2: { target: 3 } } }
źródło
level
użyłemreduce
trzeciego argumentu.level
musi to być +1 lubpath.length
-1Możemy użyć funkcji rekurencyjnej:
/** * 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) }
To jest prostsze!
źródło
obj[path[0]] = value;
ponieważpath
jest zawsze typustring[]
, nawet jeśli został tylko 1 ciąg.obj[['a']] = 'new value'
. Sprawdź kod: jsfiddle.net/upsdne03Po prostu piszę małą funkcję przy użyciu rekurencji ES6 +, aby osiągnąć cel.
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');
Dużo go używałem do reagowania na stan aktualizacji, działało całkiem dobrze dla mnie.
źródło
this.updateStateProp(obj[head], value, rest);
powinna byćthis.updateStateProp(obj[head], value, rest.join());
ES6 ma całkiem fajny sposób, aby to zrobić, używając obliczonej nazwy właściwości i parametru reszt .
const obj = { levelOne: { levelTwo: { levelThree: "Set this one!" } } } const updatedObj = { ...obj, levelOne: { ...obj.levelOne, levelTwo: { ...obj.levelOne.levelTwo, levelThree: "I am now updated!" } } }
Jeśli
levelThree
jest to właściwość dynamiczna, tj. Aby ustawić dowolną właściwość wlevelTwo
, możesz użyć[propertyName]: "I am now updated!"
gdziepropertyName
przechowuje nazwę właściwości wlevelTwo
.źródło
Zainspirowany odpowiedzią @ 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; }
Przykład:
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' } }
źródło
Lodash ma metodę o nazwie update która robi dokładnie to, czego potrzebujesz.
Ta metoda otrzymuje następujące parametry:
W twoim przykładzie wyglądałoby to tak:
_.update(obj, 'db.mongodb.user', function(originalValue) { return 'root' })
źródło
Stworzyłem sedno do ustawiania i pobierania wartości obj za pomocą ciągu znaków na podstawie poprawnej odpowiedzi. Możesz go pobrać lub użyć jako pakietu 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
źródło
Jeśli chcesz zmienić tylko głębiej zagnieżdżone obiekty, inną metodą może być odniesienie do obiektu. Ponieważ obiekty JS są obsługiwane przez ich odwołania, możesz utworzyć odniesienie do obiektu, do którego masz dostęp za pomocą klucza łańcuchowego.
Przykład:
// 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 } };
źródło
Innym podejściem jest użycie rekurencji do przekopania się przez obiekt:
(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"}}
źródło
Musiałem osiągnąć to samo, ale w Node.js ... Więc znalazłem ten fajny moduł: https://www.npmjs.com/package/nested-property
Przykład:
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"));
źródło
Wymyśliłem własne rozwiązanie, używając czystego es6 i rekurencji, które nie powoduje mutacji oryginalnego obiektu.
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);
źródło
Jeśli chciałbyś, aby istniała funkcja, która wymaga wcześniejszych właściwości, możesz użyć czegoś takiego, co zwróci również flagę stwierdzającą, czy udało jej się znaleźć i ustawić zagnieżdżoną właściwość.
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; }); }
źródło
Zainspirowany ClojureScript's
assoc-in
( https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L5280 ), używając rekurencji:/** * 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); }
Uwaga:
Dzięki dostarczonej realizacji,
assoc_in({"a": 1}, ["a", "b"], 2)
zwroty
{"a": 1}
Wolałbym, żeby w tym przypadku wyrzucał błąd. W razie potrzeby możesz dodać wpis,
assoc
aby sprawdzić, czym
jest to obiekt lub tablica, iw przeciwnym razie zgłosić błąd.źródło
Próbowałem napisać tę metodę w skrócie , może komuś pomóc!
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);
źródło
JQuery ma metodę rozszerzającą:
https://api.jquery.com/jquery.extend/
po prostu przekaż nadpisania jako obiekt i połączy oba.
źródło