Javascript: как динамически создавать вложенные объекты, используя имена объектов, заданные массивом
Я надеюсь, что кто-то может помочь мне с этим Javascript.
У меня есть объект под названием "Настройки", и я хотел бы написать функцию, которая добавляет новые настройки для этого объекта.
Имя и значение новой настройки предоставляются в виде строк. Строка, дающая имя параметра, затем разделяется подчеркиванием на массив. Новый параметр должен быть добавлен к существующему объекту "Настройки" путем создания новых вложенных объектов с именами, заданными каждой частью массива, кроме последней части, которая должна быть строкой, дающей значение параметра. Затем я должен иметь возможность обратиться к настройке и, например, предупредить ее значение. Я могу сделать это статичным образом, как это...
var Settings = {};
var newSettingName = "Modules_Video_Plugin";
var newSettingValue = "JWPlayer";
var newSettingNameArray = newSettingName.split("_");
Settings[newSettingNameArray[0]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]] = {};
Settings[newSettingNameArray[0]][newSettingNameArray[1]][newSettingNameArray[2]] = newSettingValue;
alert(Settings.Modules.Mediaplayers.Video.Plugin);
... часть, которая создает вложенные объекты, делает это...
Settings["Modules"] = {};
Settings["Modules"]["Video"] = {};
Settings["Modules"]["Video"]["Plugin"] = "JWPlayer";
Однако, поскольку количество частей, составляющих имя параметра, может варьироваться, например, newSettingName может быть "Modules_Floorplan_Image_Src", я хотел бы сделать это динамически, используя такую функцию, как...
createSetting (newSettingNameArray, newSettingValue);
function createSetting(setting, value) {
// code to create new setting goes here
}
Может кто-нибудь помочь мне решить, как сделать это динамически?
Я предполагаю, что там должен быть цикл for... для перехода через массив, но я не смог найти способ создания вложенных объектов.
Если у вас так далеко, большое спасибо, что нашли время, чтобы прочитать, даже если вы не можете помочь.
26 ответов
function assign(obj, keyPath, value) {
lastKeyIndex = keyPath.length-1;
for (var i = 0; i < lastKeyIndex; ++ i) {
key = keyPath[i];
if (!(key in obj))
obj[key] = {}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
Использование:
var settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');
Вставьте функцию, короткую и быструю (без рекурсии).
var createNestedObject = function( base, names ) {
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
};
// Usage:
createNestedObject( window, ["shapes", "triangle", "points"] );
// Now window.shapes.triangle.points is an empty object, ready to be used.
Он пропускает уже существующие части иерархии. Полезно, если вы не уверены, была ли иерархия уже создана.
Или же:
Более изящная версия, в которой вы можете напрямую присвоить значение последнему объекту в иерархии, и вы можете связать вызовы функций, потому что он возвращает последний объект.
// Function: createNestedObject( base, names[, value] )
// base: the object on which to create the hierarchy
// names: an array of strings contaning the names of the objects
// value (optional): if given, will be the last object in the hierarchy
// Returns: the last object in the hierarchy
var createNestedObject = function( base, names, value ) {
// If a value is given, remove the last name and keep it for later:
var lastName = arguments.length === 3 ? names.pop() : false;
// Walk the hierarchy, creating new objects where needed.
// If the lastName was removed, then the last object is not set yet:
for( var i = 0; i < names.length; i++ ) {
base = base[ names[i] ] = base[ names[i] ] || {};
}
// If a value was given, set it to the last name:
if( lastName ) base = base[ lastName ] = value;
// Return the last object in the hierarchy:
return base;
};
// Usages:
createNestedObject( window, ["shapes", "circle"] );
// Now window.shapes.circle is an empty object, ready to be used.
var obj = {}; // Works with any object other that window too
createNestedObject( obj, ["shapes", "rectangle", "width"], 300 );
// Now we have: obj.shapes.rectangle.width === 300
createNestedObject( obj, "shapes.rectangle.height".split('.'), 400 );
// Now we have: obj.shapes.rectangle.height === 400
Примечание: если ваша иерархия должна быть построена из значений, отличных от стандартных объектов (т.е. не {}
), см. также ответ TimDog ниже.
Редактировать: использует обычные циклы вместо for...in
петли. Это безопаснее в тех случаях, когда библиотека модифицирует прототип Array.
Мое решение ES2015. Сохраняет существующие значения.
const set = (obj, path, val) => {
const keys = path.split('.');
const lastKey = keys.pop();
const lastObj = keys.reduce((obj, key) =>
obj[key] = obj[key] || {},
obj);
lastObj[lastKey] = val;
};
Пример:
const obj = {'a': {'prop': {'that': 'exists'}}};
set(obj, 'a.very.deep.prop', 'value');
console.log(JSON.stringify(obj));
// {"a":{"prop":{"that":"exists"},"very":{"deep":{"prop":"value"}}}}
Использование ES6 сокращено. Установите свой путь в массив. Во-первых, вы должны обратить массив, чтобы начать заполнение объекта.
let obj = ['a','b','c'] // {a:{b:{c:{}}}
obj.reverse();
const nestedObject = obj.reduce((prev, current) => (
{[current]:{...prev}}
), {});
Еще одно рекурсивное решение:
var nest = function(obj, keys, v) {
if (keys.length === 1) {
obj[keys[0]] = v;
} else {
var key = keys.shift();
obj[key] = nest(typeof obj[key] === 'undefined' ? {} : obj[key], keys, v);
}
return obj;
};
Пример использования:
var dog = {bark: {sound: 'bark!'}};
nest(dog, ['bark', 'loudness'], 66);
nest(dog, ['woff', 'sound'], 'woff!');
console.log(dog); // {bark: {loudness: 66, sound: "bark!"}, woff: {sound: "woff!"}}
Мне нравится этот неизменный способ ES6 для установки определенного значения во вложенном поле:
const setValueToField = (fields, value) => {
const reducer = (acc, item, index, arr) => ({ [item]: index + 1 < arr.length ? acc : value });
return fields.reduceRight(reducer, {});
};
А затем используйте его для создания целевого объекта.
const targetObject = setValueToField(['one', 'two', 'three'], 'nice');
console.log(targetObject); // Output: { one: { two: { three: 'nice' } } }
Вот простая настройка ответа jlgrall, позволяющая устанавливать различные значения для каждого элемента во вложенной иерархии:
var createNestedObject = function( base, names, values ) {
for( var i in names ) base = base[ names[i] ] = base[ names[i] ] || (values[i] || {});
};
Надеюсь, поможет.
В Lodash есть метод _.set для достижения этой цели.
let obj = {}
_.set(obj, ['a', 'b', 'c', 'd'], 'e')
or
_.set(obj, 'a.b.c.d', 'e')
// which generate the following object
{
"a": {
"b": {
"c": {
"d": "e"
}
}
}
}
Вот функциональное решение для динамического создания вложенных объектов.
const nest = (path, obj) => {
const reversedPath = path.split('.').reverse();
const iter = ([head, ...tail], obj) => {
if (!head) {
return obj;
}
const newObj = {[head]: {...obj}};
return iter(tail, newObj);
}
return iter(reversedPath, obj);
}
Пример:
const data = {prop: 'someData'};
const path = 'a.deep.path';
const result = nest(path, data);
console.log(JSON.stringify(result));
// {"a":{"deep":{"path":{"prop":"someData"}}}}
Вдохновленный методом setIn ImmutableJS, который никогда не изменит оригинал. Это работает со смешанными массивами и вложенными значениями объектов.
function setIn(obj = {}, [prop, ...rest], value) {
const newObj = Array.isArray(obj) ? [...obj] : {...obj};
newObj[prop] = rest.length ? setIn(obj[prop], rest, value) : value;
return newObj;
}
var obj = {
a: {
b: {
c: [
{d: 5}
]
}
}
};
const newObj = setIn(obj, ["a", "b", "c", 0, "x"], "new");
//obj === {a: {b: {c: [{d: 5}]}}}
//newObj === {a: {b: {c: [{d: 5, x: "new"}]}}}
Внутри вашего цикла вы можете использовать
lodash.set
и создаст для вас путь:
...
const set = require('lodash.set');
const p = {};
const [type, lang, name] = f.split('.');
set(p, [lang, type, name], '');
console.log(p);
// { lang: { 'type': { 'name': '' }}}
Цените, что этот вопрос мега старый! Но, столкнувшись с необходимостью сделать что-то подобное в узле, я создал модуль и опубликовал его в npm. Nestob
var nestob = require('nestob');
//Create a new nestable object - instead of the standard js object ({})
var newNested = new nestob.Nestable();
//Set nested object properties without having to create the objects first!
newNested.setNested('biscuits.oblong.marmaduke', 'cheese');
newNested.setNested(['orange', 'tartan', 'pipedream'], { poppers: 'astray', numbers: [123,456,789]});
console.log(newNested, newNested.orange.tartan.pipedream);
//{ biscuits: { oblong: { marmaduke: 'cheese' } },
orange: { tartan: { pipedream: [Object] } } } { poppers: 'astray', numbers: [ 123, 456, 789 ] }
//Get nested object properties without having to worry about whether the objects exist
//Pass in a default value to be returned if desired
console.log(newNested.getNested('generic.yoghurt.asguard', 'autodrome'));
//autodrome
//You can also pass in an array containing the object keys
console.log(newNested.getNested(['chosp', 'umbridge', 'dollar'], 'symbols'));
//symbols
//You can also use nestob to modify objects not created using nestob
var normalObj = {};
nestob.setNested(normalObj, 'running.out.of', 'words');
console.log(normalObj);
//{ running: { out: { of: 'words' } } }
console.log(nestob.getNested(normalObj, 'random.things', 'indigo'));
//indigo
console.log(nestob.getNested(normalObj, 'improbable.apricots'));
//false
function initPath(obj, path) {
path.split('.').reduce((o, key) => (
Object.assign(o, {[key]: Object(o[key])}),
o[key]
), obj);
return obj;
}
использование
const obj = { a: { b: 'value1' } };
initPath(obj, 'a.c.d').a.c.d='value2';
/*
{
"a": {
"b": "value1",
"c": {
"d": "value2"
}
}
}
*/
Попробуйте это: https://github.com/silkyland/object-to-formdata
var obj2fd = require('obj2fd/es5').default
var fd = obj2fd({
a:1,
b:[
{c: 3},
{d: 4}
]
})
Результат:
fd = [
a => 1,
b => [
c => 3,
d => 4
]
]
Фрагмент для тех, кому необходимо создать вложенные объекты с поддержкой ключей массива, чтобы установить значение в конец пути. Путь это строка вроде: modal.product.action.review.2.write.survey.data
, Основано на версии jlgrall.
var updateStateQuery = function(state, path, value) {
var names = path.split('.');
for (var i = 0, len = names.length; i < len; i++) {
if (i == (len - 1)) {
state = state[names[i]] = state[names[i]] || value;
}
else if (parseInt(names[i+1]) >= 0) {
state = state[names[i]] = state[names[i]] || [];
}
else {
state = state[names[i]] = state[names[i]] || {};
}
}
};
Я нашел ответ @jlgrall великолепным, но после упрощения он не работал в Chrome. Вот мое исправление, если кто-то хочет облегченную версию:
var callback = 'fn.item1.item2.callbackfunction',
cb = callback.split('.'),
baseObj = window;
function createNestedObject(base, items){
$.each(items, function(i, v){
base = base[v] = (base[v] || {});
});
}
callbackFunction = createNestedObject(baseObj, cb);
console.log(callbackFunction);
Я надеюсь, что это полезно и актуально. Извините, я только что разбил этот пример...
Since I started with something from this page, I wanted to contribute back
Other examples overwrote the final node even if it was set, and that wasn't what I wanted.
Also, if
returnObj
is set to true, it returns the base object. By default,
falsy
, it returns the deepest node.
Naturally, when the numeric keys are greater than 0, you might see some
undefined
gaps.
Practical uses of this might be
function AddNote(book, page, line) {
// assume a global global notes collection
var myNotes = param(allNotes, [book, page, line], []);
myNotes.push('This was a great twist!')
return myNotes;
}
var allNotes = {}
var youthfulHopes = AddNote('A Game of Thrones', 4, 2, "I'm already hooked, at least I won't have to wait long for the books to come out!");
console.log(allNotes)
// {"A Game of Thrones": [undefined, undefined, undefined, undefined, [undefined, undefined, ["I'm already hooked, at least I won't have to wait long for the books to come out!"]]]}
console.log(youthfulHopes)
// ["I'm already hooked, at least I won't have to wait long for the books to come out!"]
простой ответ. на es6 я использую это
const assign = (obj, path, value) => {
let keyPath = path.split('.')
let lastKeyIndex = keyPath.length - 1
for (let i = 0; i < lastKeyIndex; ++i) {
let key = keyPath[i]
if (!(key in obj)) {
obj[key] = {}
}
obj = obj[key]
}
obj[keyPath[lastKeyIndex]] = value
}
пример json
const obj = {
b: 'hello'
}
вы можете добавить новый ключ
assign(obj, 'c.d.e', 'this value')
и вы получаете как рев
console.log(obj)
//response example
obj = {
b: 'hello',
c: {
d: {
e: 'this value'
}
}
}
После ответа TimDog это добавляет ценность только последнему элементу.
Результат:
{
"a": {
"b": {
"c": "val"
}
}
Вот разложение на несколько полезных функций, каждая из которых сохраняет существующие данные. Не обрабатывает массивы.
-
setDeep
: Отвечает на вопрос. Неразрушающий для других данных в объекте. -
setDefaultDeep
: То же самое, но устанавливается, только если еще не установлено. -
setDefault
: Устанавливает ключ, если он еще не установлен. То же, что и у Pythonsetdefault
. -
setStructure
: Вспомогательная функция, которая строит путь.
// Create a nested structure of objects along path within obj. Only overwrites the final value.
let setDeep = (obj, path, value) =>
setStructure(obj, path.slice(0, -1))[path[path.length - 1]] = value
// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setDefaultDeep = (obj, path, value) =>
setDefault(setStructure(obj, path.slice(0, -1)), path[path.length - 1], value)
// Set obj[key] to value if key is not in object, and return obj[key]
let setDefault = (obj, key, value) =>
obj[key] = key in obj ? obj[key] : value;
// Create a nested structure of objects along path within obj. Does not overwrite any value.
let setStructure = (obj, path) =>
path.reduce((obj, segment) => setDefault(obj, segment, {}), obj);
// EXAMPLES
let temp = {};
// returns the set value, similar to assignment
console.log('temp.a.b.c.d:',
setDeep(temp, ['a', 'b', 'c', 'd'], 'one'))
// not destructive to 'one'
setDeep(temp, ['a', 'b', 'z'], 'two')
// does not overwrite, returns previously set value
console.log('temp.a.b.z: ',
setDefaultDeep(temp, ['a', 'b', 'z'], 'unused'))
// creates new, returns current value
console.log('temp["a.1"]: ',
setDefault(temp, 'a.1', 'three'))
// can also be used as a getter
console.log("temp.x.y.z: ",
setStructure(temp, ['x', 'y', 'z']))
console.log("final object:", temp)
Я не уверен, зачем кому-то нужны строковые пути:
- Они неоднозначны для ключей с точками
- В первую очередь вам нужно построить струны
Попробуйте использовать рекурсивную функцию:
function createSetting(setting, value, index) {
if (typeof index !== 'number') {
index = 0;
}
if (index+1 == setting.length ) {
settings[setting[index]] = value;
}
else {
settings[setting[index]] = {};
createSetting(setting, value, ++index);
}
}
Установить вложенные данные:
function setNestedData(root, path, value) {
var paths = path.split('.');
var last_index = paths.length - 1;
paths.forEach(function(key, index) {
if (!(key in root)) root[key] = {};
if (index==last_index) root[key] = value;
root = root[key];
});
return root;
}
var obj = {'existing': 'value'};
setNestedData(obj, 'animal.fish.pet', 'derp');
setNestedData(obj, 'animal.cat.pet', 'musubi');
console.log(JSON.stringify(obj));
// {"existing":"value","animal":{"fish":{"pet":"derp"},"cat":{"pet":"musubi"}}}
Получить вложенные данные:
function getNestedData(obj, path) {
var index = function(obj, i) { return obj && obj[i]; };
return path.split('.').reduce(index, obj);
}
getNestedData(obj, 'animal.cat.pet')
// "musubi"
getNestedData(obj, 'animal.dog.pet')
// undefined
Я думаю, это короче
Settings = {};
newSettingName = "Modules_Floorplan_Image_Src";
newSettingValue = "JWPlayer";
newSettingNameArray = newSettingName.split("_");
a = Settings;
for (var i = 0 in newSettingNameArray) {
var x = newSettingNameArray[i];
a[x] = i == newSettingNameArray.length-1 ? newSettingValue : {};
a = a[x];
}
Вы можете определить свои собственные методы Object; также я использую подчеркивание для краткости:
var _ = require('underscore');
// a fast get method for object, by specifying an address with depth
Object.prototype.pick = function(addr) {
if (!_.isArray(addr)) return this[addr]; // if isn't array, just get normally
var tmpo = this;
while (i = addr.shift())
tmpo = tmpo[i];
return tmpo;
};
// a fast set method for object, put value at obj[addr]
Object.prototype.put = function(addr, val) {
if (!_.isArray(addr)) this[addr] = val; // if isn't array, just set normally
this.pick(_.initial(addr))[_.last(addr)] = val;
};
Пример использования:
var obj = {
'foo': {
'bar': 0 }}
obj.pick('foo'); // returns { bar: 0 }
obj.pick(['foo','bar']); // returns 0
obj.put(['foo', 'bar'], -1) // obj becomes {'foo': {'bar': -1}}
Eval, вероятно, излишний, но результат прост для визуализации, без вложенных циклов или рекурсии.
function buildDir(obj, path){
var paths = path.split('_');
var final = paths.pop();
for (let i = 1; i <= paths.length; i++) {
var key = "obj['" + paths.slice(0, i).join("']['") + "']"
console.log(key)
eval(`${key} = {}`)
}
eval(`${key} = '${final}'`)
return obj
}
var newSettingName = "Modules_Video_Plugin_JWPlayer";
var Settings = buildDir( {}, newSettingName );
В основном вы постепенно пишете строку "obj['one']= {}", "obj['one']['two']"= {}
и оценивая это;