Test auf Existenz eines verschachtelten JavaScript-Objektschlüssels


691

Wenn ich einen Verweis auf ein Objekt habe:

var test = {};

das wird möglicherweise (aber nicht sofort) verschachtelte Objekte haben, so etwas wie:

{level1: {level2: {level3: "level3"}}};

Was ist der beste Weg, um das Vorhandensein von Eigentum in tief verschachtelten Objekten zu überprüfen?

alert(test.level1);ergibt undefined, aberalert(test.level1.level2.level3); scheitert .

Ich mache gerade so etwas:

if(test.level1 && test.level1.level2 && test.level1.level2.level3) {
    alert(test.level1.level2.level3);
}

aber ich habe mich gefragt, ob es einen besseren Weg gibt.


1
Vielleicht möchten Sie eine tangential verwandte Frage überprüfen, die kürzlich gestellt wurde stackoverflow.com/questions/2525943/…
Anurag


Ein paar Vorschläge dort: stackoverflow.com/a/18381564/1636522
Blatt

Ihr aktueller Ansatz hat ein potenzielles Problem, wenn die Eigenschaft der Ebene 3 falsch ist. In diesem Fall wird ein Beispiel in diesem Fall angezeigt. Bitte schauen
GibboK

10
Sie können einfach auch try catch verwenden
Raghavendra

Antworten:


487

Sie müssen es Schritt für Schritt tun, wenn Sie keine möchten, TypeErrordenn wenn eines der Mitglieder nulloder istundefined eine Ausnahme ausgelöst wird, ist und Sie versuchen, auf ein Mitglied zuzugreifen.

Sie können entweder einfach catchdie Ausnahme oder eine Funktion zum Testen der Existenz mehrerer Ebenen erstellen:

function checkNested(obj /*, level1, level2, ... levelN*/) {
  var args = Array.prototype.slice.call(arguments, 1);

  for (var i = 0; i < args.length; i++) {
    if (!obj || !obj.hasOwnProperty(args[i])) {
      return false;
    }
    obj = obj[args[i]];
  }
  return true;
}

var test = {level1:{level2:{level3:'level3'}} };

checkNested(test, 'level1', 'level2', 'level3'); // true
checkNested(test, 'level1', 'level2', 'foo'); // false

ES6 UPDATE:

Hier ist eine kürzere Version der ursprünglichen Funktion, die ES6-Funktionen und Rekursion verwendet (auch in richtigen Tail-Call- Form):

function checkNested(obj, level,  ...rest) {
  if (obj === undefined) return false
  if (rest.length == 0 && obj.hasOwnProperty(level)) return true
  return checkNested(obj[level], ...rest)
}

Wenn Sie jedoch den Wert einer verschachtelten Eigenschaft abrufen und nicht nur ihre Existenz überprüfen möchten, finden Sie hier eine einfache einzeilige Funktion:

function getNested(obj, ...args) {
  return args.reduce((obj, level) => obj && obj[level], obj)
}

const test = { level1:{ level2:{ level3:'level3'} } };
console.log(getNested(test, 'level1', 'level2', 'level3')); // 'level3'
console.log(getNested(test, 'level1', 'level2', 'level3', 'length')); // 6
console.log(getNested(test, 'level1', 'level2', 'foo')); // undefined
console.log(getNested(test, 'a', 'b')); // undefined

Mit der obigen Funktion können Sie den Wert verschachtelter Eigenschaften abrufen, andernfalls wird zurückgegeben undefined.

UPDATE 2019-10-17:

Der optionale Verkettungsvorschlag hat Stufe 3 des ECMAScript-Ausschussprozesses erreicht. Auf diese Weise können Sie mithilfe des Tokens ?., des neuen optionalen Verkettungsoperators , sicher auf tief verschachtelte Eigenschaften zugreifen :

const value = obj?.level1?.level2?.level3 

Wenn eine der Ebenen ist, auf die zugegriffen wird, nulloder undefinedder Ausdruck sich undefinedvon selbst auflöst .

Mit dem Vorschlag können Sie auch Methodenaufrufe sicher verarbeiten:

obj?.level1?.method();

Der obige Ausdruck erzeugt, undefinedwenn obj, obj.level1oder obj.level1.methodsind nulloder undefined, sonst ruft er die Funktion auf.

Sie können mit dieser Funktion mit Babel über das optionale Verkettungs-Plugin spielen .

Seit Babel 7.8.0 wird ES2020 standardmäßig unterstützt

Überprüfen Sie dieses Beispiel auf der Babel REPL.

🎉🎉UPDATE: Dezember 2019 🎉🎉

Der optionale Verkettungsvorschlag erreichte schließlich Stufe 4 in der Sitzung des TC39-Ausschusses im Dezember 2019. Dies bedeutet, dass diese Funktion Teil des ECMAScript 2020- Standards sein wird.


4
argumentsist eigentlich kein Array. Array.prototype.slice.call(arguments)konvertiert es in ein formales Array. Learn
Deefour

23
Dies wäre viel effizienter zu tun var obj = arguments[0];und zu beginnen, var i = 1anstatt das argumentsObjekt zu kopieren
Claudiu

2
Ich habe aus Sparmaßnahmen eine Version mit try / catch zusammengestellt, und keine Überraschung - die Leistung ist schrecklich (außer in Safari aus irgendeinem Grund). Im Folgenden finden Sie einige Antworten, die ziemlich performant sind, zusammen mit Claudius Modifikation, die auch deutlich performanter ist als die ausgewählte Antwort. Siehe jsperf hier jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

3
In ES6 kann die argsVariablendeklaration entfernt und ...args als zweites Argument für die checkNestedMethode verwendet werden. developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Vernon

6
Dies ist eine sehr unerreichbare. Wenn sich Eigenschaftsschlüssel ändern (werden sie), müssten alle Entwickler im Projekt die gesamte Codebasis nach Zeichenfolgen durchsuchen. Dies ist keine wirkliche Lösung für das Problem, da es ein viel größeres Problem einführt
Drenai

356

Hier ist ein Muster, das ich von Oliver Steele aufgenommen habe :

var level3 = (((test || {}).level1 || {}).level2 || {}).level3;
alert( level3 );

In der Tat ist dieser ganze Artikel eine Diskussion darüber, wie Sie dies in Javascript tun können. Er beschließt, die obige Syntax (die nicht so schwer zu lesen ist, wenn man sich erst einmal daran gewöhnt hat) als Redewendung zu verwenden.


8
@wared Ich denke, es ist vor allem deshalb interessant, wie prägnant es ist. Eine ausführliche Beschreibung der Leistungsmerkmale finden Sie im verlinkten Beitrag. Ja, es werden immer alle Tests durchgeführt, aber es wird vermieden, temporäre Variablen zu erstellen, und Sie können einen Alias ​​{} für eine Variable festlegen, wenn Sie den Aufwand vermeiden möchten, jedes Mal ein neues leeres Objekt zu erstellen. In 99% der Fälle würde ich nicht erwarten, dass Geschwindigkeit eine Rolle spielt, und in Fällen, in denen dies der Fall ist, gibt es keinen Ersatz für die Profilerstellung.
Gabe Moothart

9
@ MuhammadUmer Nein, der Punkt (test || {})ist, dass wenn der Test undefiniert ist, Sie es tun ({}.level1 || {}). Natürlich {}.level1ist undefiniert, das heißt, Sie tun es {}.level2und so weiter.
Joshua Taylor

3
@JoshuaTaylor: Ich denke, er meint, wenn testes nicht deklariert ist, wird es einen ReferenceError geben , aber das ist kein Problem, denn wenn es nicht deklariert ist, muss ein Fehler behoben werden, also ist der Fehler eine gute Sache.

33
Sie sagten , „was nicht ist so schwer zu lesen , wenn Sie gewöhnen , um es“ . Nun, das sind Anzeichen, von denen Sie bereits wissen, dass dies ein Chaos ist . Warum dann diese Lösung vorschlagen? Es ist anfällig für Tippfehler und gibt absolut nichts zur Lesbarkeit. Schau es dir an! Wenn ich muss eine hässliche Zeile schreiben, sollte es asswell sein lesbar ; Also bleibe ich einfach beiif(test.level1 && test.level1.level2 && test.level1.level2.level3)
Sharky

8
Wenn mir nichts fehlt, funktioniert dies nicht für boolesche Endeigenschaften, die möglicherweise falsch sind ... leider. Ansonsten liebe ich diese Redewendung.
T3db0t

261

Aktualisieren

Sieht so aus, als hätte lodash für alle Ihre Bedürfnisse nach verschachtelten Immobilien hinzugefügt _.get .

_.get(countries, 'greece.sparta.playwright')

https://lodash.com/docs#get


Vorherige Antwort

Benutzer von lodash können sich an lodash.contrib erfreuen, das über einige Methoden verfügt, mit denen dieses Problem behoben werden kann .

getPath

Unterschrift: _.getPath(obj:Object, ks:String|Array)

Ruft den Wert in einer beliebigen Tiefe in einem verschachtelten Objekt basierend auf dem Pfad ab, der durch die angegebenen Schlüssel beschrieben wird. Schlüssel können als Array oder als durch Punkte getrennte Zeichenfolge angegeben werden. Gibt zurück, undefinedwenn der Pfad nicht erreicht werden kann.

var countries = {
        greece: {
            athens: {
                playwright:  "Sophocles"
            }
        }
    }
};

_.getPath(countries, "greece.athens.playwright");
// => "Sophocles"

_.getPath(countries, "greece.sparta.playwright");
// => undefined

_.getPath(countries, ["greece", "athens", "playwright"]);
// => "Sophocles"

_.getPath(countries, ["greece", "sparta", "playwright"]);
// => undefined

Lodash benötigt wirklich eine _.isPathDefined (obj, pathString) -Methode.
Matthew Payne

@MatthewPayne Es wäre vielleicht schön, aber es ist wirklich nicht notwendig. Sie könnten es wirklich leicht selbst tunfunction isPathDefined(object, path) { return typeof _.getPath(object, path) !== 'undefined'; }
Thor84no

11
Lodash hat die gleiche Funktionalität selbst:_.get(countries, 'greece.sparta.playwright', 'default'); // → 'default' _.has(countries, 'greece.spart.playwright') // → false
Tom

noch besser wäre _.result
Shishir Arora

Wenn Sie mehrere verschiedene Pfade bestimmen müssen, berücksichtigen Sie: var url = _.get(e, 'currentTarget.myurl', null) || _.get(e, 'currentTarget.attributes.myurl.nodeValue', null) || null
Simon Hutchison

209

Ich habe Leistungstests (danke cdMinix für das Hinzufügen von lodash) für einige der zu dieser Frage vorgeschlagenen Vorschläge mit den unten aufgeführten Ergebnissen durchgeführt.

Haftungsausschluss Nr. 1 Das Verwandeln von Zeichenfolgen in Referenzen ist eine unnötige Metaprogrammierung und wird wahrscheinlich am besten vermieden. Verlieren Sie zunächst nicht den Überblick über Ihre Referenzen. Lesen Sie mehr aus dieser Antwort auf eine ähnliche Frage .

Haftungsausschluss Nr. 2 Wir sprechen hier von Millionen von Vorgängen pro Millisekunde. Es ist sehr unwahrscheinlich, dass einer dieser Punkte in den meisten Anwendungsfällen einen großen Unterschied macht. Wählen Sie, was am sinnvollsten ist, wenn Sie die jeweiligen Einschränkungen kennen. Für mich würde ich mit so etwas wie reduceaus Bequemlichkeit gehen.

Objektverpackung (von Oliver Steele) - 34% - am schnellsten

var r1 = (((test || {}).level1 || {}).level2 || {}).level3;
var r2 = (((test || {}).level1 || {}).level2 || {}).foo;

Ursprüngliche Lösung (in Frage vorgeschlagen) - 45%

var r1 = test.level1 && test.level1.level2 && test.level1.level2.level3;
var r2 = test.level1 && test.level1.level2 && test.level1.level2.foo;

checkNested - 50%

function checkNested(obj) {
  for (var i = 1; i < arguments.length; i++) {
    if (!obj.hasOwnProperty(arguments[i])) {
      return false;
    }
    obj = obj[arguments[i]];
  }
  return true;
}

get_if_exist - 52%

function get_if_exist(str) {
    try { return eval(str) }
    catch(e) { return undefined }
}

validChain - 54%

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

objHasKeys - 63%

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

nestedPropertyExists - 69%

function nestedPropertyExists(obj, props) {
    var prop = props.shift();
    return prop === undefined ? true : obj.hasOwnProperty(prop) ? nestedPropertyExists(obj[prop], props) : false;
}

_.get - 72%

deeptest - 86%

function deeptest(target, s){
    s= s.split('.')
    var obj= target[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

traurige Clowns - 100% - am langsamsten

var o = function(obj) { return obj || {} };

var r1 = o(o(o(o(test).level1).level2).level3);
var r2 = o(o(o(o(test).level1).level2).foo);

16
Es ist zu beachten, dass je mehr% ein Test hat - desto langsamer ist
avalanche1

2
was ist mit lodash _.get()? Wie performant ist es im Vergleich zu diesen Antworten?
Beniutek

1
Jede dieser Methoden ist je nach Situation langsamer oder schneller als andere. Wenn alle Schlüssel gefunden wurden, ist "Object Wrap" möglicherweise am schnellsten. Wenn jedoch einer der Schlüssel nicht gefunden wird, ist "Native Solution / Original Solution" möglicherweise schneller.
EvilReiko

1
@evilReiko Jede Methode ist langsamer, wenn keine Schlüssel gefunden werden, aber im Verhältnis zueinander ist sie immer noch ziemlich gleich. Sie haben jedoch Recht - dies ist mehr eine intellektuelle Übung als alles andere. Wir sprechen hier von einer Million Iterationen pro Millisekunde. Ich sehe keinen Anwendungsfall, bei dem es einen großen Unterschied machen würde. Ich persönlich würde mich für reduceoder try/catchaus Bequemlichkeit entscheiden.
Unitario

Wie performant ist es im Vergleich zutry { test.level1.level2.level3 } catch (e) { // some logger e }
Lex

46

Sie können eine Objekteigenschaft in jeder Tiefe lesen, wenn Sie den Namen wie eine Zeichenfolge behandeln : 't.level1.level2.level3'.

window.t={level1:{level2:{level3: 'level3'}}};

function deeptest(s){
    s= s.split('.')
    var obj= window[s.shift()];
    while(obj && s.length) obj= obj[s.shift()];
    return obj;
}

alert(deeptest('t.level1.level2.level3') || 'Undefined');

Es wird zurückgegeben, undefinedwenn eines der Segmente ist undefined.


3
Es ist erwähnenswert, dass diese Methode zumindest in Chrome sehr leistungsfähig ist und in einigen Fällen die von @Claudiu modifizierte Version der ausgewählten Antwort übertrifft. Siehe Leistungstest hier: jsperf.com/check-if-deep-property-exists-with-willnotthrow
netpoetica

28
var a;

a = {
    b: {
        c: 'd'
    }
};

function isset (fn) {
    var value;
    try {
        value = fn();
    } catch (e) {
        value = undefined;
    } finally {
        return value !== undefined;
    }
};

// ES5
console.log(
    isset(function () { return a.b.c; }),
    isset(function () { return a.b.c.d.e.f; })
);

Wenn Sie in ES6 Umgebung Codierung (oder mit 6to5 ) , dann können Sie die Vorteile der nehmen Pfeil Funktion Syntax:

// ES6 using the arrow function
console.log(
    isset(() => a.b.c),
    isset(() => a.b.c.d.e.f)
);

In Bezug auf die Leistung gibt es keine Leistungseinbußen für die Verwendung von try..catchBlock, wenn die Eigenschaft festgelegt ist. Wenn die Eigenschaft nicht festgelegt ist, wirkt sich dies auf die Leistung aus.

Betrachten Sie einfach _.has:

var object = { 'a': { 'b': { 'c': 3 } } };

_.has(object, 'a');
// → true

_.has(object, 'a.b.c');
// → true

_.has(object, ['a', 'b', 'c']);
// → true

2
Ich denke, der try-catchAnsatz ist die beste Antwort. Es gibt einen philosophischen Unterschied zwischen der Abfrage eines Objekts nach seinem Typ und der Annahme, dass die API vorhanden ist, und dem entsprechenden Fehlschlagen, wenn dies nicht der Fall ist. Letzteres ist in lose typisierten Sprachen besser geeignet. Siehe stackoverflow.com/a/408305/2419669 . Der try-catchAnsatz ist auch viel klarer als if (foo && foo.bar && foo.bar.baz && foo.bar.baz.qux) { ... }.
Yangmillstheory

24

wie wäre es mit

try {
   alert(test.level1.level2.level3)
} catch(e) {
 ...whatever

}

15
Ich denke nicht, dass try / catch ein guter Weg ist, um die Existenz eines Objekts zu testen: try / catch soll Ausnahmen behandeln, nicht normale Bedingungen wie den Test hier. Ich denke, (typeof foo == "undefined") ist bei jedem Schritt besser - und im Allgemeinen ist wahrscheinlich ein Refactoring erforderlich, wenn Sie mit so tief verschachtelten Eigenschaften arbeiten. Außerdem führt try / catch zu einer Unterbrechung von Firebug (und in jedem Browser, in dem Break-On-Fehler aktiviert ist), wenn eine Ausnahme ausgelöst wird.
Sam Dutton

Ich stimme darüber ab, da der Browser die Existenz zweimal überprüft, wenn Sie andere Lösungen verwenden. Nehmen wir an, Sie möchten ´acb = 2´ nennen. Der Browser muss das Vorhandensein überprüfen, bevor der Wert geändert wird (andernfalls handelt es sich um einen vom Betriebssystem abgefangenen Speicherfehler).

4
Es bleibt die Frage: Wodurch ist es für Browser schneller, einen Versuch zu starten oder hasOwnProperty()n-mal anzurufen ?

14
Warum ist das wieder schlimm? Das sieht für mich am saubersten aus.
Austin Pray

Ich würde sagen: Wenn Sie erwarten, dass die Eigenschaft existiert, ist es in Ordnung, sie in einen try-Block zu packen. Wenn es dann nicht existiert, ist es ein Fehler. Wenn Sie jedoch nur faul sind und regulären Code in den catch-Block einfügen, falls die Eigenschaft nicht vorhanden ist, wird try / catch missbraucht. Hier ist ein if / else oder ähnliches erforderlich.
Robsch

18

ES6 Antwort, gründlich getestet :)

const propExists = (obj, path) => {
    return !!path.split('.').reduce((obj, prop) => {
        return obj && obj[prop] ? obj[prop] : undefined;
    }, obj)
}

→ siehe Codepen mit vollständiger Testabdeckung


Ich habe dafür gesorgt, dass Ihre Tests fehlgeschlagen sind, indem Sie den Wert der flachen Stütze auf 0 gesetzt haben. Sie müssen sich um Typzwang kümmern.
Germain

@germain Funktioniert das für Sie? (Ich vergleiche explizit ===die verschiedenen Falsys und füge einen Test hinzu. Wenn Sie eine bessere Idee haben, lassen Sie es mich wissen.)
Frank Nocke

Ich habe Ihre Tests erneut fehlgeschlagen und den Wert der flachen Stütze auf gesetzt false. Und dann möchten Sie vielleicht einen Wert in Ihrem Objekt haben, auf den gesetzt ist undefined(ich weiß, dass es seltsam ist, aber es ist JS). Ich habe einen positiven falschen Wert eingestellt auf 'Prop not Found':const hasTruthyProp = prop => prop === 'Prop not found' ? false : true const path = obj => path => path.reduce((obj, prop) => { return obj && obj.hasOwnProperty(prop) ? obj[prop] : 'Prop not found' }, obj) const myFunc = compose(hasTruthyProp, path(obj))
Germain

Können Sie meinen Codepen (oben rechts, einfach) aufteilen, Tests korrigieren und hinzufügen und mir Ihre URL senden? Danke =)
Frank Nocke

Flucht in eine (riesige) Bibliothek von Drittanbietern ... möglich, aber nicht meine Präferenz.
Frank Nocke

17

Sie können auch den optionalen Verkettungsvorschlag tc39 zusammen mit babel 7 - verwenden. tc39-Vorschlag verwenden

Code würde so aussehen:

  const test = test?.level1?.level2?.level3;
  if (test) alert(test);

Beachten Sie, dass sich diese Syntax mit ziemlicher Sicherheit ändern wird, da einige TC39-Mitglieder Einwände haben.
jhpratt GOFUNDME RELICENSING

Wahrscheinlich, aber dies wird in irgendeiner Form in der Zeit verfügbar sein, und das ist das einzige, was zählt. Es ist eine der Funktionen, die ich in JS am meisten vermisse.
Goran.it

11

Ich habe einen rekursiven Ansatz versucht:

function objHasKeys(obj, keys) {
  var next = keys.shift();
  return obj[next] && (! keys.length || objHasKeys(obj[next], keys));
}

Die ! keys.length ||Rekursion wird beendet, sodass die Funktion nicht ausgeführt wird, ohne dass noch Tasten zum Testen übrig sind. Tests:

obj = {
  path: {
    to: {
      the: {
        goodKey: "hello"
      }
    }
  }
}

console.log(objHasKeys(obj, ['path', 'to', 'the', 'goodKey'])); // true
console.log(objHasKeys(obj, ['path', 'to', 'the', 'badKey']));  // undefined

Ich verwende es, um eine benutzerfreundliche HTML-Ansicht einer Reihe von Objekten mit unbekannten Schlüsseln / Werten zu drucken, z.

var biosName = objHasKeys(myObj, 'MachineInfo:BiosInfo:Name'.split(':'))
             ? myObj.MachineInfo.BiosInfo.Name
             : 'unknown';

9

Ich denke, das folgende Skript bietet eine besser lesbare Darstellung.

deklariere eine Funktion:

var o = function(obj) { return obj || {};};

dann benutze es so:

if (o(o(o(o(test).level1).level2).level3)
{

}

Ich nenne es "traurige Clown-Technik", weil sie das Zeichen o (


BEARBEITEN:

Hier ist eine Version für TypeScript

Es gibt Typprüfungen zur Kompilierungszeit (sowie die Intellisense, wenn Sie ein Tool wie Visual Studio verwenden).

export function o<T>(someObject: T, defaultValue: T = {} as T) : T {
    if (typeof someObject === 'undefined' || someObject === null)
        return defaultValue;
    else
        return someObject;
}

Die Verwendung ist die gleiche:

o(o(o(o(test).level1).level2).level3

aber diesmal funktioniert Intellisense!

Außerdem können Sie einen Standardwert festlegen:

o(o(o(o(o(test).level1).level2).level3, "none")

1
°0o <°(())))><
Daniel W.

1
Ich mag dieses, weil es ehrlich ist und ein "undefiniertes" in dein Gesicht wirft, wenn du deinen ObjectTyp nicht kennst . +1.

1
Solange Sie die Aussage in parens halten, können Sie es auch glückliche Clown-Technik nennen (o
Sventies

Danke Sventies. Ich liebe deinen Kommentar. Es ist ein sehr schöner Blickwinkel - solche Bedingungen werden meistens in "Wenns" verwendet und sind immer von externen Klammern umgeben. Also, ja, es ist meistens ein glücklicher Clown :)))
VeganHunter

Sie müssen wirklich in Klammern verliebt sein, um diese zu
wählen

7

Erstellen Sie ein globales functionund verwenden Sie es im gesamten Projekt

Versuche dies

function isExist(arg){
   try{
      return arg();
   }catch(e){
      return false;
   }
}

let obj={a:5,b:{c:5}};

console.log(isExist(()=>obj.b.c))
console.log(isExist(()=>obj.b.foo))
console.log(isExist(()=>obj.test.foo))

wenn Bedingung

if(isExist(()=>obj.test.foo)){
   ....
}

Funktioniert super. Einfach und effizient
gbland777

6

Ein einfacher Weg ist folgender:

try {
    alert(test.level1.level2.level3);
} catch(e) {
    alert("undefined");    // this is optional to put any output here
}

Das try/catchfängt die Fälle ab, in denen eines der übergeordneten Objekte wie test, test.level1, test.level1.level2 nicht definiert ist.


6

Ich habe kein Beispiel für jemanden gesehen, der Proxies verwendet

Also habe ich mir meine eigene ausgedacht. Das Tolle daran ist, dass Sie keine Zeichenfolgen interpolieren müssen. Sie können tatsächlich eine kettenfähige Objektfunktion zurückgeben und einige magische Dinge damit tun. Sie können sogar Funktionen aufrufen und Array-Indizes abrufen, um nach tiefen Objekten zu suchen

Der obige Code funktioniert gut für synchrone Inhalte. Aber wie würden Sie etwas Asynchrones wie diesen Ajax-Aufruf testen? Wie testest du das? Was ist, wenn die Antwort nicht json ist, wenn ein 500-http-Fehler zurückgegeben wird?

window.fetch('https://httpbin.org/get')
.then(function(response) {
  return response.json()
})
.then(function(json) {
  console.log(json.headers['User-Agent'])
})

Sicher, Sie könnten async / await verwenden, um einige Rückrufe zu entfernen. Aber was wäre, wenn Sie es noch magischer machen könnten? etwas, das so aussieht:

fetch('https://httpbin.org/get').json().headers['User-Agent']

Sie fragen sich wahrscheinlich, wo sich alle Versprechen und .thenKetten befinden ... dies könnte für alles, was Sie wissen, blockieren ... aber mit der gleichen Proxy-Technik mit Versprechen können Sie tatsächlich tief verschachtelte komplexe Pfade auf ihre Existenz testen, ohne jemals eine einzige Funktion zu schreiben


Wenn jemand interessiert ist, habe ich die asynchrone Version auf npm
Endless

5

Basierend auf dieser Antwort habe ich diese generische Funktion entwickelt, mit ES2015der das Problem gelöst werden kann

function validChain( object, ...keys ) {
    return keys.reduce( ( a, b ) => ( a || { } )[ b ], object ) !== undefined;
}

var test = {
  first: {
    second: {
        third: "This is not the key your are looking for"
    }
  }
}

if ( validChain( test, "first", "second", "third" ) ) {
    console.log( test.first.second.third );
}

1
Hier ist mein letzter Ansatzfunction validChain (object, path) { return path.split('.').reduce((a, b) => (a || { })[b], object) !== undefined }
James Harrington

5

Ich habe eine kleine Funktion erstellt, um verschachtelte Objekteigenschaften sicher abzurufen.

function getValue(object, path, fallback, fallbackOnFalsy) {
    if (!object || !path) {
        return fallback;
    }

    // Reduces object properties to the deepest property in the path argument.
    return path.split('.').reduce((object, property) => {
       if (object && typeof object !== 'string' && object.hasOwnProperty(property)) {
            // The property is found but it may be falsy.
            // If fallback is active for falsy values, the fallback is returned, otherwise the property value.
            return !object[property] && fallbackOnFalsy ? fallback : object[property];
        } else {
            // Returns the fallback if current chain link does not exist or it does not contain the property.
            return fallback;
        }
    }, object);
}

Oder eine einfachere, aber etwas unlesbare Version:

function getValue(o, path, fb, fbFalsy) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? !o[p] && fbFalsy ? fb : o[p] : fb, o);
}

Oder noch kürzer, aber ohne Rückfall auf die falsche Flagge:

function getValue(o, path, fb) {
   if(!o || !path) return fb;
   return path.split('.').reduce((o, p) => o && typeof o !== 'string' && o.hasOwnProperty(p) ? o[p] : fb, o);
}

Ich habe Test mit:

const obj = {
    c: {
        a: 2,
        b: {
            c: [1, 2, 3, {a: 15, b: 10}, 15]
        },
        c: undefined,
        d: null
    },
    d: ''
}

Und hier sind einige Tests:

// null
console.log(getValue(obj, 'c.d', 'fallback'));

// array
console.log(getValue(obj, 'c.b.c', 'fallback'));

// array index 2
console.log(getValue(obj, 'c.b.c.2', 'fallback'));

// no index => fallback
console.log(getValue(obj, 'c.b.c.10', 'fallback'));

Um den gesamten Code mit der Dokumentation und den Tests zu sehen, die ich ausprobiert habe, können Sie meinen Github-Inhalt überprüfen: https://gist.github.com/vsambor/3df9ad75ff3de489bbcb7b8c60beebf4#file-javascriptgetnestedvalues-js


4

Eine kürzere ES5-Version der hervorragenden Antwort von @ CMS:

// Check the obj has the keys in the order mentioned. Used for checking JSON results.  
var checkObjHasKeys = function(obj, keys) {
  var success = true;
  keys.forEach( function(key) {
    if ( ! obj.hasOwnProperty(key)) {
      success = false;
    }
    obj = obj[key];
  })
  return success;
}

Mit einem ähnlichen Test:

var test = { level1:{level2:{level3:'result'}}};
utils.checkObjHasKeys(test, ['level1', 'level2', 'level3']); // true
utils.checkObjHasKeys(test, ['level1', 'level2', 'foo']); // false

Das einzige Problem dabei ist, wenn es mehrere Ebenen von undefinierten Schlüsseln gibt, dann erhalten Sie einen TypeError, zBcheckObjHasKeys(test, ['level1', 'level2', 'asdf', 'asdf']);
JKS

1
Eine geeignetere Methode ist jede , deren Wert direkt zurückgegeben werden kann.
RobG

Vielleicht wechseln success = false;zu return false. Sie sollten aussteigen, sobald Sie wissen, dass es kaputt geht. Nichts Tieferes kann existieren, wenn es null oder undefiniert ist. Dies würde die Fehler auf den tiefer verschachtelten Elementen verhindern, da sie offensichtlich auch nicht vorhanden sind.
Wade

4

Ich denke, das ist eine leichte Verbesserung (wird ein 1-Liner):

   alert( test.level1 && test.level1.level2 && test.level1.level2.level3 )

Dies funktioniert, weil der Operator && den endgültigen Operanden zurückgibt, den er ausgewertet hat (und der kurzgeschlossen wird).


4

Ich habe nach dem Wert gesucht, der zurückgegeben werden soll, wenn die Eigenschaft vorhanden ist, daher habe ich die Antwort von CMS oben geändert. Folgendes habe ich mir ausgedacht:

function getNestedProperty(obj, key) {
  // Get property array from key string
  var properties = key.split(".");

  // Iterate through properties, returning undefined if object is null or property doesn't exist
  for (var i = 0; i < properties.length; i++) {
    if (!obj || !obj.hasOwnProperty(properties[i])) {
      return;
    }
    obj = obj[properties[i]];
  }

  // Nested property found, so return the value
  return obj;
}


Usage:

getNestedProperty(test, "level1.level2.level3") // "level3"
getNestedProperty(test, "level1.level2.foo") // undefined


3

Die von CMS gegebene Antwort funktioniert auch mit der folgenden Änderung für Nullprüfungen einwandfrei

function checkNested(obj /*, level1, level2, ... levelN*/) 
      {
             var args = Array.prototype.slice.call(arguments),
             obj = args.shift();

            for (var i = 0; i < args.length; i++) 
            {
                if (obj == null || !obj.hasOwnProperty(args[i]) ) 
                {
                    return false;
                }
                obj = obj[args[i]];
            }
            return true;
    }

3

Ausgehend von dieser Antwort wurden folgende Optionen ausgearbeitet . Gleicher Baum für beide:

var o = { a: { b: { c: 1 } } };

Stoppen Sie die Suche, wenn Sie nicht definiert sind

var u = undefined;
o.a ? o.a.b ? o.a.b.c : u : u // 1
o.x ? o.x.y ? o.x.y.z : u : u // undefined
(o = o.a) ? (o = o.b) ? o.c : u : u // 1

Stellen Sie sicher, dass jede Ebene einzeln ist

var $ = function (empty) {
    return function (node) {
        return node || empty;
    };
}({});

$($(o.a).b).c // 1
$($(o.x).y).z // undefined

3

Ich weiß, dass diese Frage alt ist, aber ich wollte eine Erweiterung anbieten, indem ich sie allen Objekten hinzufüge. Ich weiß, dass die Leute dazu neigen, den Objektprototyp für erweiterte Objektfunktionen zu verwenden, aber ich finde nichts einfacher als dies. Außerdem ist dies jetzt mit der Object.defineProperty- Methode zulässig .

Object.defineProperty( Object.prototype, "has", { value: function( needle ) {
    var obj = this;
    var needles = needle.split( "." );
    for( var i = 0; i<needles.length; i++ ) {
        if( !obj.hasOwnProperty(needles[i])) {
            return false;
        }
        obj = obj[needles[i]];
    }
    return true;
}});

Um nun auf eine Eigenschaft in einem Objekt zu testen, können Sie einfach Folgendes tun:

if( obj.has("some.deep.nested.object.somewhere") )

Hier ist eine jsfiddle zum Testen, und insbesondere enthält sie eine jQuery, die unterbrochen wird, wenn Sie den Object.prototype direkt ändern, weil die Eigenschaft aufzählbar wird. Dies sollte mit Bibliotheken von Drittanbietern gut funktionieren.


3

Dies funktioniert mit allen Objekten und Arrays :)

Ex:

if( obj._has( "something.['deep']['under'][1][0].item" ) ) {
    //do something
}

Dies ist meine verbesserte Version von Brians Antwort

Ich habe _has als Eigenschaftsnamen verwendet, da dies zu Konflikten mit vorhandenen has-Eigenschaften führen kann ( z. B. maps).

Object.defineProperty( Object.prototype, "_has", { value: function( needle ) {
var obj = this;
var needles = needle.split( "." );
var needles_full=[];
var needles_square;
for( var i = 0; i<needles.length; i++ ) {
    needles_square = needles[i].split( "[" );
    if(needles_square.length>1){
        for( var j = 0; j<needles_square.length; j++ ) {
            if(needles_square[j].length){
                needles_full.push(needles_square[j]);
            }
        }
    }else{
        needles_full.push(needles[i]);
    }
}
for( var i = 0; i<needles_full.length; i++ ) {
    var res = needles_full[i].match(/^((\d+)|"(.+)"|'(.+)')\]$/);
    if (res != null) {
        for (var j = 0; j < res.length; j++) {
            if (res[j] != undefined) {
                needles_full[i] = res[j];
            }
        }
    }

    if( typeof obj[needles_full[i]]=='undefined') {
        return false;
    }
    obj = obj[needles_full[i]];
}
return true;
}});

Hier ist die Geige


3

Hier ist meine Meinung dazu - die meisten dieser Lösungen ignorieren den Fall eines verschachtelten Arrays wie in:

    obj = {
        "l1":"something",
        "l2":[{k:0},{k:1}],
        "l3":{
            "subL":"hello"
        }
    }

Ich möchte vielleicht nachsehen obj.l2[0].k

Mit der folgenden Funktion können Sie dies tun deeptest('l2[0].k',obj)

Die Funktion gibt true zurück, wenn das Objekt vorhanden ist, andernfalls false

function deeptest(keyPath, testObj) {
    var obj;

    keyPath = keyPath.split('.')
    var cKey = keyPath.shift();

    function get(pObj, pKey) {
        var bracketStart, bracketEnd, o;

        bracketStart = pKey.indexOf("[");
        if (bracketStart > -1) { //check for nested arrays
            bracketEnd = pKey.indexOf("]");
            var arrIndex = pKey.substr(bracketStart + 1, bracketEnd - bracketStart - 1);
            pKey = pKey.substr(0, bracketStart);
			var n = pObj[pKey];
            o = n? n[arrIndex] : undefined;

        } else {
            o = pObj[pKey];
        }
        return o;
    }

    obj = get(testObj, cKey);
    while (obj && keyPath.length) {
        obj = get(obj, keyPath.shift());
    }
    return typeof(obj) !== 'undefined';
}

var obj = {
    "l1":"level1",
    "arr1":[
        {"k":0},
        {"k":1},
        {"k":2}
    ],
    "sub": {
       	"a":"letter A",
        "b":"letter B"
    }
};
console.log("l1: " + deeptest("l1",obj));
console.log("arr1[0]: " + deeptest("arr1[0]",obj));
console.log("arr1[1].k: " + deeptest("arr1[1].k",obj));
console.log("arr1[1].j: " + deeptest("arr1[1].j",obj));
console.log("arr1[3]: " + deeptest("arr1[3]",obj));
console.log("arr2: " + deeptest("arr2",obj));


3

Jetzt können wir auch reduceverschachtelte Schlüssel durchlaufen:

// @params o<object>
// @params path<string> expects 'obj.prop1.prop2.prop3'
// returns: obj[path] value or 'false' if prop doesn't exist

const objPropIfExists = o => path => {
  const levels = path.split('.');
  const res = (levels.length > 0) 
    ? levels.reduce((a, c) => a[c] || 0, o)
    : o[path];
  return (!!res) ? res : false
}

const obj = {
  name: 'Name',
  sys: { country: 'AU' },
  main: { temp: '34', temp_min: '13' },
  visibility: '35%'
}

const exists = objPropIfExists(obj)('main.temp')
const doesntExist = objPropIfExists(obj)('main.temp.foo.bar.baz')

console.log(exists, doesntExist)


3

Sie können dies mit der rekursiven Funktion tun. Dies funktioniert auch dann, wenn Sie nicht alle Namen der verschachtelten Objektschlüssel kennen.

function FetchKeys(obj) {
    let objKeys = [];
    let keyValues = Object.entries(obj);
    for (let i in keyValues) {
        objKeys.push(keyValues[i][0]);
        if (typeof keyValues[i][1] == "object") {
            var keys = FetchKeys(keyValues[i][1])
            objKeys = objKeys.concat(keys);
        }
    }
    return objKeys;
}

let test = { level1: { level2: { level3: "level3" } } };
let keyToCheck = "level2";
let keys = FetchKeys(test); //Will return an array of Keys

if (keys.indexOf(keyToCheck) != -1) {
    //Key Exists logic;
}
else {
    //Key Not Found logic;
}

2

Hier auf der Codeabode (safeRead) gibt es eine Funktion , die dies auf sichere Weise erledigt ... dh

safeRead(test, 'level1', 'level2', 'level3');

Wenn eine Eigenschaft null oder undefiniert ist, wird eine leere Zeichenfolge zurückgegeben


Ich mag diese Methode mit Vorlagen, weil sie eine leere Zeichenfolge zurückgibt, wenn sie nicht gesetzt ist
Lounge9

2

Basierend auf einem vorherigen Kommentar ist hier eine andere Version, in der das Hauptobjekt ebenfalls nicht definiert werden konnte:

// Supposing that our property is at first.second.third.property:
var property = (((typeof first !== 'undefined' ? first : {}).second || {}).third || {}).property;

2

Ich habe meine eigene Funktion geschrieben, die den gewünschten Pfad einnimmt und eine gute und eine schlechte Rückruffunktion hat.

function checkForPathInObject(object, path, callbackGood, callbackBad){
    var pathParts = path.split(".");
    var currentObjectPath = object;

    // Test every step to see if it exists in object
    for(var i=0; i<(pathParts.length); i++){
        var currentPathPart = pathParts[i];
        if(!currentObjectPath.hasOwnProperty(pathParts[i])){
            if(callbackBad){
                callbackBad();
            }
            return false;
        } else {
            currentObjectPath = currentObjectPath[pathParts[i]];
        }
    }

    // call full path in callback
    callbackGood();
}

Verwendungszweck:

var testObject = {
    level1:{
        level2:{
            level3:{
            }
        }
    }
};


checkForPathInObject(testObject, "level1.level2.level3", function(){alert("good!")}, function(){alert("bad!")}); // good

checkForPathInObject(testObject, "level1.level2.level3.levelNotThere", function(){alert("good!")}, function(){alert("bad!")}); //bad

Ich bin fair, um Ihnen die Inspiration zu
würdigen

2
//Just in case is not supported or not included by your framework
//***************************************************
Array.prototype.some = function(fn, thisObj) {
  var scope = thisObj || window;
  for ( var i=0, j=this.length; i < j; ++i ) {
    if ( fn.call(scope, this[i], i, this) ) {
      return true;
    }
  }
  return false;
};
//****************************************************

function isSet (object, string) {
  if (!object) return false;
  var childs = string.split('.');
  if (childs.length > 0 ) {
    return !childs.some(function (item) {
      if (item in object) {
        object = object[item]; 
        return false;
      } else return true;
    });
  } else if (string in object) { 
    return true;
  } else return false;
}

var object = {
  data: {
    item: {
      sub_item: {
        bla: {
          here : {
            iam: true
          }
        }
      }
    }
  }
};

console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'x')); // false
console.log(isSet(object,'data.sub_item')); // false
console.log(isSet(object,'data.item')); // true
console.log(isSet(object,'data.item.sub_item.bla.here.iam')); // true
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.