Objekt automatisch erstellen, wenn es nicht definiert ist

76

Gibt es eine einfache Möglichkeit, Objekten automatisch Eigenschaften hinzuzufügen, wenn diese noch nicht vorhanden sind?

Betrachten Sie das folgende Beispiel:

var test = {}
test.hello.world = "Hello doesn't exist!"

Dies funktioniert nicht, weil helloes nicht definiert ist.

Der Grund, warum ich dies frage, ist, dass ich einige vorhandene Objekte habe, für die ich nicht weiß, ob sie bereits vorhanden sind hellooder nicht. Ich habe tatsächlich viele dieser Objekte in verschiedenen Teilen meines Codes. Es ist sehr ärgerlich, immer zu überprüfen, ob es helloexistiert und ob kein neues Objekt erstellt wird wie:

var test = {}
if(test.hello === undefined) test.hello = {}
test.hello.world = "Hello World!"

Gibt es eine Möglichkeit, ein Objekt wie helloin diesem Beispiel automatisch zu erstellen ?

Ich meine so etwas in PHP:

$test = array();  
$test['hello']['world'] = "Hello world";   
var_dump($test);

Ausgabe:

array(1) {
  ["hello"]=>
  array(1) {
    ["world"]=>
    string(11) "Hello world"
  }
}

Ok, es ist ein Array, aber in js Arrays ist es das gleiche Problem wie bei Objekten.

Marcel Gwerder
quelle
Funktion existcheckthingy (x, y, z) {if (x === undefined) x = {}; xy = z; } verwendet als existcheckthingy (test.hello, world, "Hallo existiert nicht!");
Bobbybee
2
@ Bobbybee Das wird nicht funktionieren. Es wird ein neues Objekt xim Bereich der existcheckthingyFunktion erstellt, das dann jedoch nicht an das testObjekt angehängt wird. Sie können es besser machen, indem Sie die "Array-ähnliche" Notation verwenden:existcheckthingy(a,x,y,z) { if (a[x] === undefined) a[x] = {}; a[x][y] = z;}
Jeff
Mögliches Duplikat: stackoverflow.com/questions/7069584/…
Maxim Zhukov
@ Jeff Ups, vergessen, dass: 3
Bobbybee
1
Betrachten Sie lodash 'Set'
Rene Wooller

Antworten:

132
var test = {};
test.hello = test.hello || {};
test.hello.world = "Hello world!";

Wenn test.helloes nicht definiert ist, wird es auf ein leeres Objekt gesetzt.

Wenn test.hellozuvor definiert, bleibt es unverändert.

var test = {
  hello : {
    foobar : "Hello foobar"
  }
};

test.hello = test.hello || {};
test.hello.world = "Hello World";

console.log(test.hello.foobar); // this is still defined;
console.log(test.hello.world); // as is this.
xbonez
quelle
Dies ist ziemlich idiomatisch JS
Alnitak
Es ist sehr kurz, danke dafür. Aber es ist immer noch eine manuelle Überprüfung. Ist es nicht möglicherweise ohne?
Marcel Gwerder
3
@MarcelGwerder nein, das ist so kurz wie es nur geht.
Alnitak
@MarcelGwerder: Nicht so weit ich weiß.
Xbonez
1
@xbonez NB: nicht "definiert" - wahr . Ein leeres Objekt ist jedoch tatsächlich wahr.
Alnitak
14

Neues Objekt

myObj = {};

rekursive Funktion

function addProps(obj, arr, val) {

    if (typeof arr == 'string')
        arr = arr.split(".");

    obj[arr[0]] = obj[arr[0]] || {};

    var tmpObj = obj[arr[0]];

    if (arr.length > 1) {
        arr.shift();
        addProps(tmpObj, arr, val);
    }
    else
        obj[arr[0]] = val;

    return obj;

}

Nennen Sie es mit einer punktnotierten Zeichenfolge

addProps(myObj, 'sub1.sub2.propA', 1);

oder mit einem Array

addProps(myObj, ['sub1', 'sub2', 'propA'], 1);

und Ihr Objekt wird so aussehen

myObj = {
  "sub1": {
    "sub2": {
      "propA": 1
    }
  }
};

Es funktioniert auch mit nicht leeren Objekten!

Kolumbus
quelle
Dies funktioniert wirklich sehr gut und aktualisiert den korrekten Wert, ohne mein Objekt zu beschädigen.
RozzA
6

Nun, Sie könnten den Prototyp von Objectum eine Funktion erweitern, die eine Eigenschaft zurückgibt, diese aber zuerst hinzufügt, wenn sie nicht vorhanden ist:

Object.prototype.getOrCreate = function (prop) {
    if (this[prop] === undefined) {
        this[prop] = {};
    }
    return this[prop];
};

var obj = {};

obj.getOrCreate("foo").getOrCreate("bar").val = 1;
Basilikum
quelle
1
Dies scheint eine großartige Lösung zu sein, bricht jedoch aus irgendeinem Grund DataTables. Wird das Spielen mit Objektinternalen normalerweise als gute Idee angesehen?
Jason Lewis
Aus diesem Grund kann das Erweitern nativer Objekte eine schlechte Idee sein, da sich Methoden möglicherweise überschneiden.
Tschallacka
6

Ohne eine Funktion ist dies nicht möglich, da JavaScript keine generische Getter / Setter-Methode für Objekte hat (z. B. Python __getattr__). Hier ist eine Möglichkeit, dies zu tun:

function add_property(object, key, value) {
    var keys = key.split('.');

    while (keys.length > 1) {
        var k = keys.shift();

        if (!object.hasOwnProperty(k)) {
            object[k] = {};
        }

        object = object[k];
    }

    object[keys[0]] = value;
}

Wenn Sie wirklich möchten, können Sie es dem Prototyp von hinzufügen Object. Sie können es so nennen:

> var o = {}
> add_property(o, 'foo.bar.baz', 12)
> o.foo.bar.baz
12
Mixer
quelle
Mit dieser Funktion stimmt etwas nicht, wenn sie wiederholt für dasselbe Objekt verwendet wird
RozzA
@RozzA: Danke, dass du diesen Fehler entdeckt hast, er ist jetzt behoben.
Blender
@Blender gibt es eine Möglichkeit, den Wert der Eigenschaft zu lesen, wie read_property(o, 'foo.bar.baz')12 zurückgeben wird? nachdem eine Eigenschaft hinzugefügt wurde
Karthikeyan Vedi
5

Hier ist eine coole Version mit Proxies:

const myUpsert = (input) => {
    const handler = {
        get: (obj, prop) => {
            obj[prop] = obj[prop] || {};
            return myUpsert(obj[prop]);
        }
    };
    return new Proxy(input, handler);
};

Und du benutzt es so:

myUpsert(test).hello.world = '42';

Dadurch werden alle fehlenden Eigenschaften als leere Objekte hinzugefügt und die vorhandenen bleiben unberührt. Es ist wirklich nur eine Proxy-Version des Klassikers test.hello = test.hello || {}, wenn auch viel langsamer ( siehe Benchmark hier ). Aber es ist auch viel schöner anzusehen, besonders wenn Sie es mehr als eine Ebene tiefer machen. Ich würde es nicht für leistungsintensives Daten-Crunching auswählen, aber es ist wahrscheinlich schnell genug für ein Front-End-Status-Update (wie in Redux).

Beachten Sie, dass es hier einige implizite Annahmen gibt:

  1. Die dazwischenliegenden Eigenschaften sind entweder Objekte oder nicht vorhanden. Dies wird test.hellozum Beispiel ersticken, wenn es sich um eine Zeichenfolge handelt.
  2. Dass Sie dies immer tun möchten, solange Sie den Proxy anstelle des ursprünglichen Objekts verwenden.

Diese können ziemlich leicht gemildert werden, wenn Sie sie nur in gut begrenzten Kontexten (wie einem Reduzierungskörper) verwenden, in denen die Wahrscheinlichkeit gering ist, dass der Proxy versehentlich zurückgegeben wird, und nicht viel anderes, was Sie mit dem Objekt tun möchten.

John Neuhaus
quelle
2
var test = {}
if(!test.hasOwnProperty('hello')) {
    test.hello = {};
}
test.hello.world = "Hello World!"
Shawn31313
quelle
2

Dadurch wird eine Eigenschaft hinzugefügt, helloderen Wert {world: 'Hello world!'}dem Testobjekt entspricht, falls es nicht vorhanden ist. Wenn Sie viele dieser Objekte haben, können Sie sie einfach durchlaufen und diese Funktion anwenden. Hinweis: Verwendet lodash.js

var test = {};
_.defaults(test, { hello: {world: 'Hello world!'} });    

Welches ist eigentlich eine bequeme Methode, um zu sagen:

var defaults = _.partialRight(_.assign, function(a, b) {
  return typeof a == 'undefined' ? b : a;
});        
defaults(test, { hello: {world: 'Hello world!'} });

Hinweis: _.defaults Verwendet Schleifen, um dasselbe wie beim zweiten Block zu erreichen.

PS Checkout https://stackoverflow.com/a/17197858/1218080

holographisches Prinzip
quelle
1
_.set ({}, 'abcd', "asdf") erstellt '{"a": {"b": {"c": {"d": "asdf"}}} und könnte sein mehr was er suchte
Rene Wooller
1

Ich habe mir etwas ausgedacht, das auch wirklich benutzerdefiniert ist, aber es funktioniert soweit ich es getestet habe.

function dotted_put_var(str,val) {
    var oper=str.split('.');
    var p=window;
    for (var i=0;i<oper.length-1;i++) {
        var x=oper[i];
        p[x]=p[x]||{};
        p=p[x];
    }
    p[oper.pop()]=val;
}

Dann kann eine komplexe Variable wie folgt festgelegt werden, um sicherzustellen, dass alle Links erstellt werden, wenn nicht bereits:

dotter_put_var('test.hello.world', 'testvalue'); // test.hello.world="testvalue";

Siehe diese funktionierende FIDDLE .

Frederik.L
quelle
1

Ich benutze das:

Object.prototype.initProperty = function(name, defaultValue) {
  if (!(name in this)) this[name] = defaultValue;
};

Sie können später zB tun:

var x = {a: 1};
x.initProperty("a", 2); // will not change property a
x.initProperty("b", 3); // will define property b
console.log(x); // => {a: 1, b: 3}
Michal Politzer
quelle
1
var test = {}
test.hello.world = "Hello doesn't exist!"

Dies wird offensichtlich einen Fehler auslösen, da Sie die Datei test.hello nicht definiert haben

Zuerst müssen Sie den Hallo-Schlüssel definieren, dann können Sie im Inneren einen beliebigen Schlüssel zuweisen. Wenn Sie jedoch einen Schlüssel erstellen möchten, falls dieser nicht vorhanden ist, können Sie Folgendes tun

test.hello = test.hello || {};

Mit der obigen Anweisung wird das test.hello-Objekt erstellt, wenn es nicht definiert ist. Wenn es definiert ist, wird derselbe Wert wie zuvor zugewiesen

Jetzt können Sie einen neuen Schlüssel in der Datei test.hello zuweisen

test.hello.world = "Everything works perfect";

test.hello.world2 = 'With another key too, it works perfect';
VIKAS KOHLI
quelle
1

let test = {};
test = {...test, hello: {...test.hello, world: 'Hello does exist!'}};
console.log(test);

Wenn Sie den Spread-Operator verwenden, kann der Wert undefiniert sein. Es wird automatisch ein Objekt erstellt.

Nick
quelle
0

Ich habe einige Änderungen an der Antwort von Columbus vorgenommen , um das Erstellen von Arrays zu ermöglichen:

function addProps(obj, arr, val) {

  if (typeof arr == 'string')
    arr = arr.split(".");

  var tmpObj, isArray = /^(.*)\[(\d+)\]$/.exec(arr[0])
  if (isArray && !Number.isNaN(isArray[2])) {
    obj[isArray[1]] = obj[isArray[1]] || [];
    obj[isArray[1]][isArray[2]] = obj[isArray[1]][isArray[2]] || {}
    tmpObj = obj[isArray[1]][isArray[2]];
  } else {
    obj[arr[0]] = obj[arr[0]] || {};
    tmpObj = obj[arr[0]];
  }

  if (arr.length > 1) {
    arr.shift();
    addProps(tmpObj, arr, val);
  } else
    obj[arr[0]] = val;

  return obj;

}


var myObj = {}
addProps(myObj, 'sub1[0].sub2.propA', 1)
addProps(myObj, 'sub1[1].sub2.propA', 2)

console.log(myObj)

Ich denke, dass es möglich ist, mit "sub1 []. Sub2 ..." nur in das sub1Array zu pushen , anstatt den Index anzugeben, aber das reicht mir jetzt.

Alisson Nunes
quelle