「未定義のプロパティを読み取れません」エラーを回避するにはどうすればよいですか?


118

私のコードでは、多くのオブジェクトが相互にネストされているエントリを持つ配列を扱います。次のようになります。

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

時々配列を反復処理する必要があるため、これは私に問題を引き起こしています、そして矛盾は私にそのようなエラーを投げています:

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

私は言うことができることは承知していますがif(a.b){ console.log(a.b.c)}、5つまたは6つまでのオブジェクトが互いにネストされている場合、これは非常に面倒です。エラーをスローせずにconsole.logが存在する場合にのみそれを実行できるようにする他の(より簡単な)方法はありますか?


3
エラーはおそらく通常のJavascript例外であるため、try..catchステートメントを試してください。とはいえ、非常に異種の要素を含む配列は、設計上の問題のように見えます。
ミリムース2013

3
構造がアイテム間で一貫していない場合、存在の確認の何が問題になっていますか?本当に、私は使用しますif ("b" in a && "c" in a.b)。それは「退屈な」かもしれませんが、それはあなたが矛盾のために得るものです...通常のロジック。
2013

2
存在しないプロパティにアクセスするのはなぜですか、オブジェクトがどのように見えるのか知りませんか?
Bergi

9
なぜ誰かがすべてをクラッシュさせるエラーを望まないのか理解できます。オブジェクトのプロパティが存在するかどうかに常に依存するわけではありません。オブジェクトが不正な形式であるというイベントを処理できるものがある場合、コードははるかに効率的で脆弱ではありません。
SSH

3
実際の状況でいくつのオブジェクト/配列が不正な形式になっているのか驚くでしょう
OneMoreQuestion

回答:


120

更新

  • ECMAScript 2020以降に従ってJavaScriptを使用する場合は、オプションのチェーンを参照してください。
  • TypeScriptは、バージョン3.7でオプションのチェーンのサポートを追加しました。
// use it like this
obj?.a?.lot?.of?.properties

ECMASCript 2020以前のJavaScriptまたはバージョン3.7より古いTypeScriptの解決策

簡単な回避策は、ES6 矢印関数でtry / catchヘルパー関数を使用することです。

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

作業スニペット:

詳細については、この記事を参照してください。


2
大好きです!追加するのは、catch内のconsole.warnだけです。そのため、エラーはわかりますが、続行されます。
Rabbi Shuki Gur 2018

再スローせずにすべての例外をキャッチすることは悪いことであり、一般に、予期される実行フローの一部として例外を使用することも、この場合はかなり適切に抑制されていますが、優れていません。
hugo

50

あなたがやっていることは例外を発生させます(そして当然のことですが)。

いつでもできる

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

しかし、私はそうではなく、代わりにあなたのユースケースを考えます。

なじみのない6レベルのネストされたデータにアクセスするのはなぜですか?これを正当化するユースケースは何ですか?

通常、実際に処理するオブジェクトの種類を検証します。

また、サイドノートif(a.b)では、abが0の場合、または "0"の場合でもfalseを返すため、次のようなステートメントは使用しないでください。代わりにa.b !== undefined


1
最初の編集に関して:正当化されます。私はJSON構造化データベースエントリを処理しています。オブジェクトは複数レベルのフィールド(つまり、entries.users.messages.dateなど、すべてのケースでデータが入力されているわけではありません)をスケーリングします
Ari

「abが0の場合はtrueを返します-いいえ。typeof a.b === "undefined" && a.b!=null-最初の後で2番目の部分を実行する必要はありません。実行するだけの方が理にかなっていますif ("b" in a)
Ian

@イアンええ、私は明らかにそれを逆に意味しました、abが「0」であってもfalseを返します。ナイスキャッチ
ベンジャミングレンバウム2013

@BenjaminGruenbaumいいですね、あなたがそれを意味しているかどうかはわかりませんでした。また、私はあなたがしたいと思いますtypeof a.b !!予告- ==「未定義」&& AB = null`なので!==
イアン・

3
退屈なab && abc && console.log(abc)を使いたくない場合は、これが不明を一貫してログに記録する唯一の方法です。
ブライアンクレイ

14

私があなたの質問を正しく理解している場合、オブジェクトにプロパティが含まれているかどうかを判断する最も安全な方法が必要です。

最も簡単な方法は、in演算子を使用することです。

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

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

もちろん、これは好きなだけネストできます

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

これが役立つかどうかはわかりません。


9
これを安全にネストすることはできません。window.b未定義の場合はどうなりますか?タイプエラーが発生します:Cannot use 'in' operator to search for 'c' in undefined
Trevor

13

使用している場合はlodashを、あなたは彼らの「持っている」機能を使用することができます。ネイティブの「in」に似ていますが、パスを使用できます。

var testObject = {a: {b: {c: 'walrus'}}};
if(_.has(testObject, 'a.b.c')) {
  //Safely access your walrus here
}

2
ベストは、我々が使用することができます_.get()簡単に読み取りのためにデフォルトで:_.get(object, 'a.b.c', 'default');
IFNOT

12

これを試して。a.bが定義されていない場合は、if例外なくステートメントを残します。

if (a.b && a.b.c) {
  console.log(a.b.c);
}

5

これは、深いjsonオブジェクトまたは複雑なjsonオブジェクトを操作する場合の一般的な問題なので、コードを読みにくくする複数のチェックを試行/キャッチしたり埋め込んだりしないようにします。通常、この小さなコードをすべてのプロセスで使用して仕事をします。

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

5

あなたが持っている場合はlodashあなたは、その使用することができます.get方法を

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

またはそれにデフォルト値を与える

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

4

私はundefsafeを宗教的に使用しています。要求した値を取得するか、「未定義」を返すまで、オブジェクトの各レベルをテストします。しかし、決してエラーにはなりません。


2
これはlodashに似ています_.get
Filype

いい叫び!lodashの他の機能が必要ない場合でも、引き続き役立ちます。
martinedwards


3

Cao Shouguangの答えは好きですが、関数をパラメーターとしてgetSafe関数に渡すたびに、呼び出しを行うのが好きではありません。単純なパラメーターと純粋なES5を受け入れるようにgetSafe関数を変更しました。

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

それは実際には動作しませんgetSafe(myObj.x.c)。ChromeとFirefoxの最新バージョンを試しました。
RickardElimää19年

2

strの答えでは、プロパティが未定義の場合、設定されたデフォルト値の代わりに値「undefined」が返されます。これによりバグが発生する場合があります。次の例では、プロパティまたはオブジェクトが定義されていない場合に常にdefaultValが返されるようにします。

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

Hardy Le Rouxによる私のコードの改良バージョンは、let myObj = {} getSafe(()=> myObj.ab、 "nice")では機能しませんが、私のコードでは機能します。誰かが理由を説明しますか?
曹操光

2

Lodashには、get以下に示すように、オプションの3番目のパラメーターとしてデフォルトを許可するメソッドがあります。

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>


2

がnullでxない場合にのみ一連の関数を適用したいとしますx

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

次に、同じことをする必要があるとしましょうy

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

そして同じz

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

適切な抽象化なしでわかるように、コードを何度も複製することになります。そのような抽象化はすでに存在しています:多分モナド。

たぶんモナドは値と計算コンテキストの両方を保持します。

  1. モナドは値を安全に保ち、それに関数を適用します。
  2. 計算コンテキストは、関数を適用する前のnullチェックです。

素朴な実装は次のようになります。

⚠️この実装は、説明のみを目的としています。これはそれを行う方法ではなく、多くのレベルで間違っています。しかし、これはあなたが私が話していることのより良い考えをあなたに与えるでしょう。

ご覧のとおり、何も壊れることはありません。

  1. 一連の関数を値に適用します
  2. いずれかの時点で値がnull(または未定義)になると、関数は適用されなくなります。

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>


付録1

これはこの投稿の目的ではないので、モナドが何であるかを説明することはできません。しかし、エリック・エリオットが過去のブログ記事で述べたように、JavaScriptモナドはシンプルになりました

スキルレベルやカテゴリ理論の理解に関係なく、モナドを使用すると、コードを扱いやすくなります。モナドを利用しないと、コードの操作が困難になる可能性があります(たとえば、コールバック地獄、ネストされた条件付きブランチ、冗長性の向上)。


付録2

これが私がMaybeモナドを使ってあなたの問題を解決する方法です

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/monet@0.9.0/dist/monet.js"></script>
<script>const {Maybe} = Monet;</script>


0

私は以前にこれに答え、たまたま今日同じようなチェックをしていました。ネストされたドット付きプロパティが存在するかどうかを確認するための簡略化。これを変更して値を返すか、デフォルトを使用して目標を達成できます。

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;

}

あなたの質問では、次のようなことができます:

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


0

プロパティを取得する前にデフォルト値を指定することで、エラーを回避できます

var test = [{'a':{'b':{'c':"foo"}}}, {'a': "bar"}];

for (i=0; i<test.length; i++) {
    const obj = test[i]
    // No error, just undefined, which is ok
    console.log(((obj.a || {}).b || {}).c);
}

これは配列でもうまくいきます:

const entries = [{id: 1, name: 'Scarllet'}]
// Giving a default name when is empty
const name = (entries.find(v => v.id === 100) || []).name || 'no-name'
console.log(name)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.