У меня есть объект, который может иметь любое количество уровней глубины и может иметь любые существующие свойства. Например:
var obj = {
db: {
mongodb: {
host: 'localhost'
}
}
};
При этом я хотел бы установить (или перезаписать) такие свойства:
set('db.mongodb.user', 'root');
// or:
set('foo.bar', 'baz');
Где строка свойств может иметь любую глубину, а значение может быть любым типом / предметом.
Объекты и массивы как значения не нужно объединять, если ключ свойства уже существует.
В предыдущем примере будет создан следующий объект:
var obj = {
db: {
mongodb: {
host: 'localhost',
user: 'root'
}
},
foo: {
bar: baz
}
};
Как я могу реализовать такую функцию?
javascript
ecmascript-5
Джон Б.
источник
источник
set('foo', 'bar'); set('foo.baz', 'qux');
, гдеfoo
сначала держится, аString
потом становитсяObject
? Что происходит'bar'
?set()
метод, и уobj.db.mongodb.user = 'root';
вас будет именно то, что вам кажется?bar
перезаписывается файломObject
. @adeneo и @rmertins В самом деле :) Но, к сожалению, мне приходится оборачивать другую логику. @Robert Levy Я нашел его и получил доступ к работе, но настройка кажется намного более сложной ...Ответы:
Эта функция, используя указанные вами аргументы, должна добавлять / обновлять данные в
obj
контейнере. Обратите внимание, что вам нужно отслеживать, какие элементы вobj
схеме являются контейнерами, а какие - значениями (строки, целые числа и т. Д.), Иначе вы начнете генерировать исключения.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');
источник
var schema = obj
а не простоobj
везде?schema
- это указатель, который перемещается по пути с помощьюschema = schema[elem]
. Итак, после цикла forschema[pList[len - 1]]
указывает на mongo.db.user вobj
.cloneDeep
функцию lodash .Lodash имеет метод _.set () .
_.set(obj, 'db.mongodb.user', 'root'); _.set(obj, 'foo.bar', 'baz');
источник
Немного поздно, но вот небиблиотечный, более простой ответ:
/** * 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); }
Эта функция, которую я сделал, может делать именно то, что вам нужно, и даже немного больше.
допустим, мы хотим изменить целевое значение, которое глубоко вложено в этот объект:
let myObj = { level1: { level2: { target: 1 } } }
Итак, мы бы назвали нашу функцию так:
setDeep(myObj, ["level1", "level2", "target1"], 3);
приведет к:
myObj = {level1: {level2: {target: 3}}}
Установка флага set recursively в значение true установит объекты, если они не существуют.
setDeep(myObj, ["new", "path", "target"], 3, true);
приведет к такому:
obj = myObj = { new: { path: { target: 3 } }, level1: { level2: { target: 3 } } }
источник
level
я использовалreduce
третий аргумент.level
должно быть +1 илиpath.length
-1Мы можем использовать функцию рекурсии:
/** * 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) }
Все проще!
источник
obj[path[0]] = value;
потому чтоpath
он всегда имеет типstring[]
, даже когда осталась только одна строка.obj[['a']] = 'new value'
. Проверьте код: jsfiddle.net/upsdne03Я просто пишу небольшую функцию, используя рекурсию ES6 + для достижения цели.
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');
Я много использовал его для реакции на состояние обновления, у меня он работал очень хорошо.
источник
this.updateStateProp(obj[head], value, rest);
должна бытьthis.updateStateProp(obj[head], value, rest.join());
В ES6 есть довольно крутой способ сделать это, используя имя вычисляемого свойства и параметр отдыха .
const obj = { levelOne: { levelTwo: { levelThree: "Set this one!" } } } const updatedObj = { ...obj, levelOne: { ...obj.levelOne, levelTwo: { ...obj.levelOne.levelTwo, levelThree: "I am now updated!" } } }
Если
levelThree
это динамическое свойство, то есть для установки любого свойства вlevelTwo
, вы можете использовать[propertyName]: "I am now updated!"
where, в которомpropertyName
содержится имя свойстваlevelTwo
.источник
На основе ответа @ 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; }
Пример:
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 есть метод под названием update который делает именно то, что вам нужно.
Этот метод получает следующие параметры:
В вашем примере это будет выглядеть так:
_.update(obj, 'db.mongodb.user', function(originalValue) { return 'root' })
источник
Я создал суть для установки и получения значений obj по строке на основе правильного ответа. Вы можете скачать его или использовать как пакет 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
источник
Если вам нужно изменить только более глубокие вложенные объекты, тогда другой метод может заключаться в ссылке на объект. Поскольку объекты JS обрабатываются их ссылками, вы можете создать ссылку на объект, к которому у вас есть доступ по строковому ключу.
Пример:
// 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 } };
источник
Другой подход - использовать рекурсию для рытья объекта:
(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"}}
источник
Мне нужно было добиться того же, но в Node.js ... Итак, я нашел этот замечательный модуль: https://www.npmjs.com/package/nested-property
Пример:
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"));
источник
Я придумал собственное решение, использующее чистый es6 и рекурсию, которая не изменяет исходный объект.
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);
источник
Если вы хотите, чтобы функция, для которой существовали предыдущие свойства, вы можете использовать что-то вроде этого, она также вернет флаг, указывающий, удалось ли ей найти и установить вложенное свойство.
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; }); }
источник
Вдохновленный ClojureScript
assoc-in
( https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L5280 ) с использованием рекурсии:/** * 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); }
Заметка:
С предоставленной реализацией,
assoc_in({"a": 1}, ["a", "b"], 2)
возвращается
{"a": 1}
Я бы предпочел, чтобы в этом случае он выдавал ошибку. При желании вы можете добавить проверку,
assoc
чтобы убедиться,m
что это объект или массив, и выдать ошибку в противном случае.источник
Попробовал вкратце написать этот метод набора , может кому то поможет!
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);
источник
JQuery имеет метод расширения:
https://api.jquery.com/jquery.extend/
просто передайте перезаписи как объект, и они объединятся.
источник