mark: javascript sub-Object erstellen falls nicht vorhanden

Hallo,

ich bin gerade am überlegen wie ich eine Funktion schreiben könnte, die es mir erlaubt sub-Objekte zu erstellen, wenn diese nicht existieren.

Folgendes Beispiel gibt natürlich eine Fehlermeldung. Aber ich hätte gerne, dass die sub-Objekte a, b, c, d, e automatisch generiert werden und einen Wert zugewiesen bekommen.

var obj = {};
obj.a.b.c.d.e = 'value';

Eine Lösung, die mir eingefallen wäre ist, dass ich die Objektkette einfach per String an eine Funktion übergebe und dann, wenn typeof sub-Objekt 'undefined' zurückgibt dieses Objekt erstelle und am Ende der Funktion dann den Wert zuweise. Nur um euch eine Idee zu vermitteln, das hätte in etwa so ausgesehen (Code ist nicht getestet):

function setObjChainVal(root, chain, val){
    var objArr = chain.split('.');

    var objArrLen = objArr.length;
    for(var i = 0; i < objArrLen; i++){
        var curObj = objArr[i];

        root[curObj] = root[curObj] || {};
        root = root[curObj];
    }

    root = val;
};

setObjChainVal(obj, 'a.b.c.d.e', 'irgendein Wert');

Für mich richt dieser, mein Code nun ziemlich nach Pfusch und ich wollte fragen, ob man das nicht eleganter lösen kann. Wichtig ist mir auch noch, dass das Ganze in Zombie-Browsern (IE8) läuft. Was ich in meinem Beispielcode auch nicht beachtet habe ist, dass ich "root" auch noch klonen müsste, da ich ansonsten den Wert von "obj" überschreibe.

lg

mark

  1. Hi,

    ich bin gerade am überlegen wie ich eine Funktion schreiben könnte, die es mir erlaubt sub-Objekte zu erstellen, wenn diese nicht existieren.

    Das gibts übrigens schon in Lodash.

    Für mich richt dieser, mein Code nun ziemlich nach Pfusch und ich wollte fragen, ob man das nicht eleganter lösen kann.

    So allgemein nicht. Wenn man den Anwendungsfall kennt vielleicht schon.

    Dein Ansatz ist erst mal in Ordnung. Nur wird die letzte Eigenschaft nie gesetzt. Am Ende der Schleife muss root[property] = val; oder ähnliches stehen. Beispiel:

    function setPath(object, path, value) {
      var properties = path.split('.');
      var currentObject = object;
      for (let i = 0, l = properties.length - 1; i < l; i++) {
        var property = properties[i];
        if (typeof currentObject[property] !== 'object') {
          var newObject = {};
          currentObject[property] = newObject;
          currentObject = newObject;
        } else {
          currentObject = currentObject[property];
        }
      }
      currentObject[property] = value;
      return object;
    }
    

    Wichtig ist mir auch noch, dass das Ganze in Zombie-Browsern (IE8) läuft.

    Das tut dein Beispiel wahrscheinlich. Es erzeugt nur Objekte und setzt Eigenschaften. Das kann sogar IE 8 ;-)

    Was ich in meinem Beispielcode auch nicht beachtet habe ist, dass ich "root" auch noch klonen müsste, da ich ansonsten den Wert von "obj" überschreibe.

    "obj" wird als Objekt-Referenz übergeben. Änderungen an "obj" verändern auch das Objekt in "root", weil beide Variablen auf dasselbe Objekt verweisen. (Wenn du "root" einen komplett neuen Wert zuweist ändert sich der Wert von "root" allerdings nicht.)

    Wenn du das nicht willst und "immutable" arbeiten willst, ja, dann müsstest du ein Deep Clone von obj erstellen. Zumindest alle Objekte die im Pfad liegen müssten geklont werden. Das klingt tatsächlich ein bißchen überkomplex.

    Frank

    1. Hey,

      Da war wohl wer schneller.

      function setPath(object, path, value) {
        var properties = path.split('.');
        var currentObject = object;
        for (let i = 0, l = properties.length - 1; i < l; i++) {
          var property = properties[i];
          if (typeof currentObject[property] !== 'object') {
            var newObject = {};
            currentObject[property] = newObject;
            currentObject = newObject;
          } else {
            currentObject = currentObject[property];
          }
        }
        currentObject[property] = value;
        return object;
      }
      

      Ich habe das jetzt nicht getestet (hast du?), aber ich habe meine Zweifel, ob das funktionieren wird.
      Nehmen wir an, path sei 'a.b.c.d.e' und value sei 'value'; So wie ich das sehe, sieht deine vorletzte Zeile so aus: currentObject['d'] = 'value'. Wobei currentObject bereits das "d"-Object ist. Das "e" wird also niemals verarbeitet.

      Nemox

    2. Hallo,

      besten Dank.

      Das gibts übrigens schon in Lodash.

      Ich möchte kein Framework für eine einzige Funktion verwenden.

      Für mich richt dieser, mein Code nun ziemlich nach Pfusch und ich wollte fragen, ob man das nicht eleganter lösen kann.

      So allgemein nicht. Wenn man den Anwendungsfall kennt vielleicht schon.

      Hmmm... ich hoffe ich bekomme das nachvollziehbar hin:

      Ich benutze Handlebars für client-seitiges templating. Dort hat jedes Template ein Model. Dieses Model ist ein Javascript Objekt welches Variablen definiert, die anschließend gerendert werden. Ich habe also ein großes Konfigurationsobjekt, welches meine default-Model definiert. Nun möchte ich dieses default-Model mit anderen Werten überschreiben. Dazu erstelle ich ein neues Objekt, welches nur die Objekte enthält welche ich überschreiben möchte.

      Anders gesagt: ich habe default-Settings als Objekt und User-Settings als eigenständiges Objekt. Die user-Settings überschreiben meine default-Settings. Damit ich in den user-Settings auf die schnelle die gleiche Datenstruktur hinbekomme als in den default-Settings benötige ich diese Funktion.

      Hmm... da fällt mir gerade ein: eine andere Möglichkeit wäre, dass ich die default-Settings klone (deep copy) und das dann meine user-Settings sind, die dann, erst im nächsten Schritt überschreibe. Damit hätte ich von vorn herein schon die gleiche Datenstruktur und müsste diese nicht erst nachbauen. Das scheint mir der bessere Weg. Sorry, dass ich nicht früher drauf gekommen bin.

      Dein Ansatz ist erst mal in Ordnung. Nur wird die letzte Eigenschaft nie gesetzt. Am Ende der Schleife muss root[property] = val; oder ähnliches stehen. Beispiel:

      Danke. Darum ging es mir. Wenn der Ansatz in Ordnung ist, dann passt das für mich. Die Lösung sah für mich nur ziemlich "hacky" aus. Vor allem die Übergabe des Pfades als String.

      lg

  2. Hey,

    ich bin gerade am überlegen wie ich eine Funktion schreiben könnte, die es mir erlaubt sub-Objekte zu erstellen, wenn diese nicht existieren.

    Eine Lösung, die mir eingefallen wäre ist, dass ich die Objektkette einfach per String an eine Funktion übergebe und dann, wenn typeof sub-Objekt 'undefined' zurückgibt dieses Objekt erstelle und am Ende der Funktion dann den Wert zuweise. Nur um euch eine Idee zu vermitteln, das hätte in etwa so ausgesehen (Code ist nicht getestet)

    Und funktioniert auch nicht.

    function setObjChainVal(root, chain, val){
        var objArr = chain.split('.');
    
        var objArrLen = objArr.length;
        for(var i = 0; i < objArrLen; i++){
            var curObj = objArr[i];
    
            root[curObj] = root[curObj] || {};
            root = root[curObj];
        }
    
        root = val;
    };
    
    setObjChainVal(obj, 'a.b.c.d.e', 'irgendein Wert');
    

    Für mich richt dieser, mein Code nun ziemlich nach Pfusch und ich wollte fragen, ob man das nicht eleganter lösen kann.

    Bestimmt!

    Wie findest du das hier?

    function setObjChainVal(root, chain, val) {
        chain = chain.split('.');
        var obj = root,
            l = chain.length - 1;
        for (var i = 0; i < l; i++)
          obj = (chain[i] in obj) ? obj[chain[i]] : obj[chain[i]] = {};
        obj[chain[l]] = val;
        return root;
    }
    
    console.log(setObjChainVal({}, 'a.b.c.d.e', 'value'));
    

    Zu beachten ist, dass du in deiner Kette auch wirklich auf Objekte oder noch nicht vorhandene Eigenschaften zugreifen musst, nicht auf Primitives. Aber ich denke, dass das für deine Zwecke vollkommen ausreicht.

    Ich entnehme deinem Code, dass du noch nicht sehr viel Erfahrung mit JS hast, also zögere bitte nicht zu fragen, falls etwas unklar an meinem Code sein sollte.

    Nemox