Javascript: So erstellen Sie dynamisch verschachtelte Objekte mithilfe von Objektnamen, die von einem Array angegeben werden


70

Ich hoffe, jemand kann mir mit diesem Javascript helfen.

Ich habe ein Objekt namens "Einstellungen" und möchte eine Funktion schreiben, die diesem Objekt neue Einstellungen hinzufügt.

Der Name und der Wert der neuen Einstellung werden als Zeichenfolgen bereitgestellt. Die Zeichenfolge mit dem Namen der Einstellung wird dann durch die Unterstriche in ein Array aufgeteilt. Die neue Einstellung sollte zum vorhandenen Objekt "Einstellungen" hinzugefügt werden, indem neue verschachtelte Objekte mit den Namen erstellt werden, die von jedem Teil des Arrays angegeben werden, mit Ausnahme des letzten Teils, der eine Zeichenfolge sein sollte, die den Wert der Einstellung angibt. Ich sollte dann in der Lage sein, mich auf die Einstellung zu beziehen und zB ihren Wert zu alarmieren. Ich kann das so statisch machen ...

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);

... der Teil, der die verschachtelten Objekte erstellt, tut dies ...

Settings["Modules"] = {};
Settings["Modules"]["Video"] = {};
Settings["Modules"]["Video"]["Plugin"] = "JWPlayer";

Da jedoch die Anzahl der Teile, aus denen der Einstellungsname besteht, variieren kann, z. B. könnte ein neuer Einstellungsname "Modules_Floorplan_Image_Src" sein, möchte ich dies dynamisch mithilfe einer Funktion wie ...

createSetting (newSettingNameArray, newSettingValue);

function createSetting(setting, value) {
    // code to create new setting goes here
}

Kann mir jemand helfen, wie ich das dynamisch machen kann?

Ich nehme an, dass es dort eine for ... -Schleife geben muss, um durch das Array zu iterieren, aber ich konnte keinen Weg finden, um die verschachtelten Objekte zu erstellen.

Wenn Sie so weit gekommen sind, vielen Dank, dass Sie sich die Zeit zum Lesen genommen haben, auch wenn Sie nicht anders können.

Antworten:


83
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;
}

Verwendung:

var settings = {};
assign(settings, ['Modules', 'Video', 'Plugin'], 'JWPlayer');

2
Nun, ich habe so ziemlich dasselbe geschrieben, außer dass ich es erklären wollte newSettingName.split('_'). Ich sehe keinen Sinn hinter doppelten Antworten, also da. Vielleicht erklären Sie Ihre Antwort besser.
Christian

Was genau sind keyPath und value in dieser Funktion?
Lachezar Raychev

Tolle Sache. Ich bin zum letzten Wert gegangen und daher gescheitert. Verstanden, gesucht und gefunden. Auf der Ebene der Eltern zu sein ist der Schlüssel und funktioniert gut.
Manohar Reddy Poreddy

keyPath bedeutet hier für das obige Beispiel, dass der Schlüssel Modules.Video.Plugin ist und der Wert JWPlayer ist, in dem json, den sie zu bauen versuchen
Manohar Reddy Poreddy

97

Setzen Sie eine Funktion kurz und schnell ein (keine Rekursion).

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.

Es werden bereits vorhandene Teile der Hierarchie übersprungen. Nützlich, wenn Sie nicht sicher sind, ob die Hierarchie bereits erstellt wurde.

Oder:

Eine schickere Version, bei der Sie den Wert direkt dem letzten Objekt in der Hierarchie zuweisen und Funktionsaufrufe verketten können, da das letzte Objekt zurückgegeben wird.

// 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

Hinweis: Wenn Ihre Hierarchie aus anderen Werten als Standardobjekten erstellt werden muss (dh nicht {}), lesen Sie auch die Antwort von TimDog weiter unten.

Bearbeiten: Verwendet reguläre Schleifen anstelle von for...inSchleifen. Dies ist sicherer, wenn eine Bibliothek den Array-Prototyp ändert.


Ich hoffe, Sie erkennen, dass Sie eine Frage beantworten, für die die akzeptierte Antwort bereits über 15 Monate alt ist ...
Bart

Es ist in Ordnung, es ist nur nicht sehr wahrscheinlich, dass die Person, die die Frage gestellt hat, immer noch nach einer Antwort sucht. Ihre Antwort ist immer noch gut (und unterscheidet sich von den anderen), also habe ich Ihnen ein paar Daumen hoch gegeben.
Bart

13
Eigentlich ist dies die beste Antwort, da sie sich wie erwartet verhält.
Adrien

@jlgrall Wenn ich dies von einer anderen Funktion aus aufrufen muss, die die eigentliche Wertzuweisung übernimmt, gibt es eine Möglichkeit, den neuen Schlüsselpfad anstelle des Schlüsselwerts (Basis) zurückzugeben?
Ken

13
Es ist nie zu spät, eine bestehende Antwort zu verbessern.
Felipe Alarcon

20

Meine ES2015-Lösung. Behält vorhandene Werte bei.

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;
};

Beispiel:

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"}}}}

11

Eine andere rekursive Lösung:

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;
};

Anwendungsbeispiel:

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!"}}

10

Die Verwendung von ES6 ist verkürzt. Legen Sie Ihren Pfad in ein Array fest. Zuerst müssen Sie das Array umkehren, um das Objekt zu füllen.

let obj = ['a','b','c'] // {a:{b:{c:{}}}
obj.reverse();

const nestedObject = obj.reduce((prev, current) => (
    {[current]:{...prev}}
), {});

1
Genial! Wie kann dies geändert werden, wenn nestedObject zuvor definiert wurde und {a: {b: {}} gesetzt hat, aber nicht abc? Dh vorhandene Schlüssel beibehalten und nur fehlende hinzufügen. Das wäre wirklich nützlich.
Temuri

6

Ich liebe diese unveränderliche ES6-Methode, um einen bestimmten Wert für verschachtelte Felder festzulegen:

const setValueToField = (fields, value) => {
  const reducer = (acc, item, index, arr) => ({ [item]: index + 1 < arr.length ? acc : value });
  return fields.reduceRight(reducer, {});
};

Verwenden Sie es dann zum Erstellen Ihres Zielobjekts.

const targetObject = setValueToField(['one', 'two', 'three'], 'nice');
console.log(targetObject); // Output: { one: { two: { three: 'nice' } } }

Perfekt. Danke dafür.
55 Cancri

nette raffinierte Verwendung von ES6
Blazes

5

Hier ist eine einfache Änderung der Antwort von jlgrall, mit der für jedes Element in der verschachtelten Hierarchie unterschiedliche Werte festgelegt werden können:

var createNestedObject = function( base, names, values ) {
    for( var i in names ) base = base[ names[i] ] = base[ names[i] ] || (values[i] || {});
};

Ich hoffe es hilft.


1
Warum um alles in der Welt funktioniert das und löscht keine vorhandenen verschachtelten Objekte?! Sie haben den Verstand geblasen, Sir.
David Angel

Ich weiß, dass dies alt ist, dachte aber, ich würde mich einschalten. Der Grund dafür ist, dass das "Basis" -Argument von einer Kopie des Verweises auf das Originalobjekt übergeben wird. Bei jeder Iteration, die von rechts nach links ausgewertet wird, wird dies zu einem Zeiger auf die Position der aktuell zugewiesenen Eigenschaft.
Chrisw

5

Hier ist eine funktionale Lösung zum dynamischen Erstellen verschachtelter Objekte.

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);
}

Beispiel:

const data = {prop: 'someData'};
const path = 'a.deep.path';
const result = nest(path, data);
console.log(JSON.stringify(result));
// {"a":{"deep":{"path":{"prop":"someData"}}}}

1

Schätzen Sie, dass diese Frage mega alt ist! Nachdem ich jedoch festgestellt hatte, dass im Knoten so etwas getan werden muss, habe ich ein Modul erstellt und es auf npm veröffentlicht. 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

0

Versuchen Sie es mit der rekursiven Funktion:

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);
  }
}

0

Ich denke, das ist kürzer:

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];
}

0

Ich fand die Antwort von @ jlgrall großartig, aber nach der Vereinfachung funktionierte sie in Chrome nicht. Hier ist mein Problem, falls jemand eine Lite-Version haben möchte:

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);

Ich hoffe das ist nützlich und relevant. Entschuldigung, ich habe gerade dieses Beispiel zerschlagen ...


0

Sie können Ihre eigenen Objektmethoden definieren. aus Gründen der Kürze verwende ich auch einen Unterstrich:

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;
};

Beispielnutzung:

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}}

0

Ein Snippet für diejenigen, die verschachtelte Objekte mit Unterstützung von Array-Schlüsseln erstellen müssen, um einen Wert am Ende des Pfads festzulegen. Pfad ist die Zeichenfolge wie : modal.product.action.review.2.write.survey.data. Basierend auf der jlgrall-Version.

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]] || {};
        }
    }
};

0

Verschachtelte Daten festlegen:

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"}}}

Verschachtelte Daten abrufen:

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

0

Eval ist wahrscheinlich übertrieben, aber das Ergebnis ist einfach zu visualisieren, ohne verschachtelte Schleifen oder Rekursionen.

 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 );

Grundsätzlich schreiben Sie nach und nach eine Zeichenfolge "obj['one']= {}", "obj['one']['two']"= {} und bewerten sie.



0

Innerhalb Ihrer Schleife können Sie lodash.setden Pfad für Sie verwenden und erstellen:

...
const set = require('lodash.set');

const p = {};
const [type, lang, name] = f.split('.');
set(p, [lang, type, name], '');

console.log(p);
// { lang: { 'type': { 'name': '' }}}

0

Hier ist eine Zerlegung in mehrere nützliche Funktionen, die jeweils vorhandene Daten beibehalten. Behandelt keine Arrays.

  • setDeep: Beantwortet Frage. Zerstörungsfrei für andere Daten im Objekt.
  • setDefaultDeep: Gleich, aber nur gesetzt, wenn nicht bereits gesetzt.
  • setDefault: Legt einen Schlüssel fest, falls nicht bereits festgelegt. Gleich wie bei Python setdefault.
  • setStructure: Hilfsfunktion, die den Pfad erstellt.

// 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)

Ich bin mir nicht sicher, warum jemand String-Pfade haben möchte:

  1. Sie sind für Schlüssel mit Punkten mehrdeutig
  2. Sie müssen zuerst die Zeichenfolgen erstellen
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.