Wie vermeide ich, dass die Eigenschaft undefinierter Fehler nicht gelesen werden kann?

117

In meinem Code beschäftige ich mich mit einem Array, das einige Einträge mit vielen ineinander verschachtelten Objekten enthält, während andere dies nicht tun. Es sieht ungefähr so ​​aus:

// where this array is hundreds of entries long, with a mix
// of the two examples given
var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];

Dies gibt mir Probleme, weil ich manchmal durch das Array iterieren muss und die Inkonsistenz mir Fehler wie folgt wirft:

for (i=0; i<test.length; i++) {
    // ok on i==0, but 'cannot read property of undefined' on i==1
    console.log(a.b.c);
}

Ich bin mir bewusst, dass ich sagen kann if(a.b){ console.log(a.b.c)}, aber dies ist außerordentlich mühsam in Fällen, in denen bis zu 5 oder 6 Objekte ineinander verschachtelt sind. Gibt es eine andere (einfachere) Möglichkeit, dass ich NUR das console.log ausführen kann, wenn es vorhanden ist, aber ohne einen Fehler auszulösen?

Ari
quelle
3
Der Fehler ist wahrscheinlich eine reguläre Javascript-Ausnahme. Versuchen Sie es also mit der try..catchAnweisung. Das heißt, ein Array, das wild heterogene Elemente enthält, scheint mir ein Designproblem zu sein.
Millimoose
3
Wenn Ihre Struktur nicht über alle Elemente hinweg konsistent ist, was ist dann falsch daran, die Existenz zu überprüfen? Wirklich, ich würde verwenden if ("b" in a && "c" in a.b). Es mag "langweilig" sein, aber das ist es, was man für Inkonsistenz bekommt ... normale Logik.
Ian
2
Warum würden Sie auf nicht vorhandene Eigenschaften zugreifen, warum wissen Sie nicht, wie die Objekte aussehen?
Bergi
9
Ich kann verstehen, warum jemand nicht möchte, dass ein Fehler alles zum Absturz bringt. Sie können sich nicht immer darauf verlassen, dass die Eigenschaften eines Objekts existieren oder nicht existieren. Wenn Sie über etwas verfügen, das das Ereignis verarbeiten kann, dass das Objekt fehlerhaft ist, ist Ihr Code viel effizienter und weniger anfällig.
SSH Diesen
3
Sie wären überrascht, wie viele Objekte / Arrays in realen Situationen fehlerhaft sind
OneMoreQuestion

Antworten:

118

Update :

  • Wenn Sie JavaScript gemäß ECMAScript 2020 oder höher verwenden, lesen Sie die optionale Verkettung .
  • TypeScript hat Unterstützung für optionale Verkettung in Version 3.7 hinzugefügt .
// use it like this
obj?.a?.lot?.of?.properties

Lösung für JavaScript vor ECMASCript 2020 oder TypeScript älter als Version 3.7 :

Eine schnelle Problemumgehung ist die Verwendung einer Try / Catch-Hilfsfunktion mit ES6- Pfeilfunktion :

function getSafe(fn, defaultVal) {
    try {
        return fn();
    } catch (e) {
        return defaultVal;
    }
}

// use it like this
getSafe(() => obj.a.lot.of.properties);

// or add an optional default value
getSafe(() => obj.a.lot.of.properties, 'nothing');

Arbeitsausschnitt:

Weitere Informationen finden Sie in diesem Artikel .

str
quelle
2
Ich liebe es! Das einzige, was ich hinzufügen würde, ist eine console.warn innerhalb des Catch, damit Sie den Fehler kennen, aber es geht weiter.
Rabbi Shuki Gur
Das Abfangen aller Ausnahmen ohne erneutes Auslösen ist schlecht , und die Verwendung von Ausnahmen als Teil des erwarteten Ausführungsflusses ist im Allgemeinen auch nicht besonders gut - auch wenn sie in diesem Fall ziemlich gut enthalten sind.
Hugo
50

Was Sie tun, löst eine Ausnahme aus (und das zu Recht).

Das kannst du immer tun

try{
   window.a.b.c
}catch(e){
   console.log("YO",e)
}

Aber ich würde nicht an Ihren Anwendungsfall denken.

Warum greifen Sie auf Daten zu, 6 Ebenen verschachtelt, die Sie nicht kennen? Welcher Anwendungsfall rechtfertigt dies?

Normalerweise möchten Sie tatsächlich überprüfen, um welche Art von Objekt es sich handelt.

Außerdem sollten Sie keine Randanweisungen wie verwenden, if(a.b)da sie false zurückgeben, wenn ab 0 ist oder sogar wenn sie "0" ist. Überprüfen Sie stattdessen, oba.b !== undefined

Benjamin Gruenbaum
quelle
1
In Bezug auf Ihre erste Bearbeitung: Es ist gerechtfertigt; Ich habe es mit strukturierten JSON-Datenbankeinträgen zu tun, so dass die Objekte mehrere Feldebenen skalieren (dh Einträge.Benutzer.Meldungen.Datum usw., in denen nicht in allen Fällen Daten eingegeben wurden)
Ari
"es wird true zurückgeben, wenn ab 0 ist" - nein. typeof a.b === "undefined" && a.b!=null- unnötig, den zweiten Teil nach dem ersten zu tun, und es ist sinnvoller, nur zu tunif ("b" in a)
Ian
@ Ian ja, ich habe es offensichtlich umgekehrt gemeint, es wird falsch zurückgegeben, auch wenn ab "0" ist. Schöner Fang
Benjamin Gruenbaum
@BenjaminGruenbaum Hört sich gut an, war mir nicht sicher, ob du das gemeint hast. Außerdem denke ich, dass Sie typeof a.b !== "undefiniert" && ab! = Null` wollen - beachten Sie die!==
Ian
3
Wenn Sie ab && abc && console.log (abc) nicht langweilig werden möchten, ist dies die einzige Möglichkeit, Unbekannte konsistent zu protokollieren.
Brian Cray
14

Wenn ich Ihre Frage richtig verstehe, möchten Sie am sichersten feststellen, ob ein Objekt eine Eigenschaft enthält.

Am einfachsten ist es, den inOperator zu verwenden .

window.a = "aString";
//window should have 'a' property
//lets test if it exists
if ("a" in window){
    //true
 }

if ("b" in window){
     //false
 }

Natürlich können Sie dies so tief verschachteln, wie Sie möchten

if ("a" in window.b.c) { }

Ich bin mir nicht sicher, ob dies hilft.

matt weiss
quelle
9
Sie können dies nicht sicher so tief verschachteln, wie Sie möchten. Was ist, wenn window.bundefiniert ist? Sie erhalten einen Tippfehler:Cannot use 'in' operator to search for 'c' in undefined
Trevor
13

Wenn Sie lodash verwenden , können Sie deren "has" -Funktion verwenden. Es ähnelt dem nativen "in", erlaubt jedoch Pfade.

var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
  //Safely access your walrus here
}
Tehwalris
quelle
2
Am besten können wir _.get()mit Standard für einfaches Lesen verwenden:_.get(object, 'a.b.c', 'default');
Ifnot
12

Versuche dies. Wenn a.bundefiniert, wird die ifAnweisung ausnahmslos verlassen.

if (a.b && a.b.c) {
  console.log(a.b.c);
}
Sean Chen
quelle
5

Dies ist ein häufiges Problem bei der Arbeit mit tiefen oder komplexen JSON-Objekten. Daher versuche ich zu vermeiden, mehrere Prüfungen zu versuchen / abzufangen oder einzubetten, die den Code unlesbar machen würden. Normalerweise verwende ich diesen kleinen Code in meinem gesamten Verfahren, um die Arbeit zu erledigen.

/* ex: getProperty(myObj,'aze.xyz',0) // return myObj.aze.xyz safely
 * accepts array for property names: 
 *     getProperty(myObj,['aze','xyz'],{value: null}) 
 */
function getProperty(obj, props, defaultValue) {
    var res, isvoid = function(x){return typeof x === "undefined" || x === null;}
    if(!isvoid(obj)){
        if(isvoid(props)) props = [];
        if(typeof props  === "string") props = props.trim().split(".");
        if(props.constructor === Array){
            res = props.length>1 ? getProperty(obj[props.shift()],props,defaultValue) : obj[props[0]];
        }
    }
    return typeof res === "undefined" ? defaultValue: res;
}
Maelkhor
quelle
5

Wenn Sie lodash haben, können Sie seine .getMethode verwenden

_.get(a, 'b.c.d.e')

oder geben Sie einen Standardwert ein

_.get(a, 'b.c.d.e', default)
Brandon Dyer
quelle
4

Ich benutze undefsafe religiös. Es testet jede Ebene in Ihrem Objekt, bis es entweder den gewünschten Wert erhält oder "undefiniert" zurückgibt. Aber niemals Fehler.

martinedwards
quelle
2
das ist ähnlich wie lodash_.get
Filype
Guter Ruf! Immer noch nützlich, wenn Sie die anderen Funktionen von lodash nicht benötigen.
Martinedwards
3

Ich mag die Antwort von Cao Shouguang, aber ich mag es nicht, bei jedem Aufruf eine Funktion als Parameter an die getSafe-Funktion zu übergeben. Ich habe die Funktion getSafe geändert, um einfache Parameter und reines ES5 zu akzeptieren.

/**
* Safely get object properties.    
* @param {*} prop The property of the object to retrieve
* @param {*} defaultVal The value returned if the property value does not exist
* @returns If property of object exists it is returned, 
*          else the default value is returned.
* @example
* var myObj = {a : {b : 'c'} };
* var value;
* 
* value = getSafe(myObj.a.b,'No Value'); //returns c 
* value = getSafe(myObj.a.x,'No Value'); //returns 'No Value'
* 
* if (getSafe(myObj.a.x, false)){ 
*   console.log('Found')
* } else {
*  console.log('Not Found') 
* }; //logs 'Not Found'
* 
* if(value = getSafe(myObj.a.b, false)){
*  console.log('New Value is', value); //logs 'New Value is c'
* }
*/
function getSafe(prop, defaultVal) {
  return function(fn, defaultVal) {
    try {
      if (fn() === undefined) {
        return defaultVal;
      } else {
        return fn();
      }
    } catch (e) {
      return defaultVal;
    }
  }(function() {return prop}, defaultVal);
}
Hardy Le Roux
quelle
Es funktioniert nicht wirklich mit getSafe(myObj.x.c). Versuchte neueste Versionen von Chrome und Firefox.
Rickard Elimää
2

In der Antwort von str wird der Wert 'undefined' anstelle des festgelegten Standardwerts zurückgegeben, wenn die Eigenschaft undefined ist. Dies kann manchmal zu Fehlern führen. Im Folgenden wird sichergestellt, dass die defaultVal immer zurückgegeben wird, wenn entweder die Eigenschaft oder das Objekt undefiniert ist.

const temp = {};
console.log(getSafe(()=>temp.prop, '0'));

function getSafe(fn, defaultVal) {
    try {
        if (fn() === undefined) {
            return defaultVal
        } else {
            return fn();
        }

    } catch (e) {
        return defaultVal;
    }
}
Cao Shouguang
quelle
Die verbesserte Version meines Codes von Hardy Le Roux funktioniert nicht mit let myObj = {} getSafe (() => myObj.ab, "nice"), während meine funktioniert. Erklärt jemand warum?
Cao Shouguang
2

Lodash verfügt über eine getMethode, die einen Standard als optionalen dritten Parameter zulässt, wie unten gezeigt:

const myObject = {
  has: 'some',
  missing: {
    vars: true
  }
}
const path = 'missing.const.value';
const myValue = _.get(myObject, path, 'default');
console.log(myValue) // prints out default, which is specified above
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>

Pureferret
quelle
2

Stellen Sie sich vor, wir möchten eine Reihe von Funktionen xgenau dann anwenden, wenn sie xnicht null sind:

if (x !== null) x = a(x);
if (x !== null) x = b(x);
if (x !== null) x = c(x);

Nehmen wir nun an, wir müssen dasselbe tun, um y:

if (y !== null) y = a(y);
if (y !== null) y = b(y);
if (y !== null) y = c(y);

Und das gleiche zu z:

if (z !== null) z = a(z);
if (z !== null) z = b(z);
if (z !== null) z = c(z);

Wie Sie ohne eine ordnungsgemäße Abstraktion sehen können, werden wir am Ende immer wieder Code duplizieren. Eine solche Abstraktion existiert bereits: die Vielleicht- Monade.

Die Vielleicht- Monade enthält sowohl einen Wert als auch einen rechnerischen Kontext:

  1. Die Monade schützt den Wert und wendet Funktionen darauf an.
  2. Der Rechenkontext ist eine Nullprüfung vor dem Anwenden einer Funktion.

Eine naive Implementierung würde folgendermaßen aussehen:

⚠️ Diese Implementierung dient nur zu Illustrationszwecken! Dies sollte nicht so gemacht werden und ist auf vielen Ebenen falsch. Dies sollte Ihnen jedoch eine bessere Vorstellung davon geben, wovon ich spreche.

Wie Sie sehen, kann nichts brechen:

  1. Wir wenden eine Reihe von Funktionen auf unseren Wert an
  2. Wenn der Wert zu irgendeinem Zeitpunkt null (oder undefiniert) wird, wenden wir einfach keine Funktion mehr an.

const abc = obj =>
  Maybe
    .of(obj)
    .map(o => o.a)
    .map(o => o.b)
    .map(o => o.c)
    .value;

const values = [
  {},
  {a: {}},
  {a: {b: {}}},
  {a: {b: {c: 42}}}
];

console.log(

  values.map(abc)

);
<script>
function Maybe(x) {
  this.value = x; //-> container for our value
}

Maybe.of = x => new Maybe(x);

Maybe.prototype.map = function (fn) {
  if (this.value == null) { //-> computational context
    return this;
  }
  return Maybe.of(fn(this.value));
};
</script>


Anhang 1

Ich kann nicht erklären, was Monaden sind, da dies nicht der Zweck dieses Beitrags ist und es Leute gibt, die dies besser können als ich. Wie Eric Elliot jedoch in seinem Blog-Beitrag sagte, wurden JavaScript-Monaden leicht gemacht :

Unabhängig von Ihrem Kenntnisstand oder Ihrem Verständnis der Kategorietheorie erleichtert die Verwendung von Monaden die Arbeit mit Ihrem Code. Wenn Sie Monaden nicht nutzen, kann es schwieriger sein, mit Ihrem Code zu arbeiten (z. B. Rückrufhölle, verschachtelte bedingte Zweige, mehr Ausführlichkeit).


Anlage 2

Hier ist, wie ich Ihr Problem mit dem lösen würde Vielleicht- Monade von

const prop = key => obj => Maybe.fromNull(obj[key]);

const abc = obj =>
  Maybe
    .fromNull(obj)
    .flatMap(prop('a'))
    .flatMap(prop('b'))
    .flatMap(prop('c'))
    .orSome('🌯')
    
const values = [
  {},
  {a: {}},
  {a: {b: {}}},
  {a: {b: {c: 42}}}
];

console.log(

  values.map(abc)

);
<script src="https://www.unpkg.com/[email protected]/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>

Customcommander
quelle
0

Ich habe das schon einmal beantwortet und heute zufällig eine ähnliche Überprüfung durchgeführt. Eine Vereinfachung, um zu überprüfen, ob eine verschachtelte gepunktete Eigenschaft vorhanden ist. Sie können dies ändern, um den Wert zurückzugeben, oder einen Standardwert, um Ihr Ziel zu erreichen.

function containsProperty(instance, propertyName) {
    // make an array of properties to walk through because propertyName can be nested
    // ex "test.test2.test.test"
    let walkArr = propertyName.indexOf('.') > 0 ? propertyName.split('.') : [propertyName];

    // walk the tree - if any property does not exist then return false
    for (let treeDepth = 0, maxDepth = walkArr.length; treeDepth < maxDepth; treeDepth++) {

        // property does not exist
        if (!Object.prototype.hasOwnProperty.call(instance, walkArr[treeDepth])) {
            return false;
        }

        // does it exist - reassign the leaf
        instance = instance[walkArr[treeDepth]];

    }

    // default
    return true;

}

In Ihrer Frage könnten Sie etwas tun wie:

let test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];
containsProperty(test[0], 'a.b.c');
matt weiss
quelle
0

Ich benutze normalerweise so:

 var x = object.any ? object.any.a : 'def';
Vansuita Jr.
quelle