【GAS/JS】すべてのオブジェクトの中にプロパティが存在するか調べる Traverse

当サイトではアフィリエイト広告を利用して商品を紹介しています。

APIからJSONを取得するとプロパティがない!

API経由でJSONを取得すると、APIの仕様によってオブジェクトのプロパティが全くないケースがあります。
GASでは存在しないプロパティを参照しようとすると、エラーになってしまうのでこれは困ります。

プロパティの存在確認としらべて出てくるのは、そのオブジェクトの直下にあるプロパティだけで、APIで取得したオブジェクトは往々にして深い位置にほしい値が存在します。

エラーが発生するのに着目してtry catchで処理をしてもいいのですがコードがイケてない!

いろいろ調べた結果下記のコードを作成しました。

英語ではこのような処理をTraverseというらしいです。

コード class Traverse

/**
 * Note: 元ネタ
 * https://stackoverflow.com/questions/722668/traverse-all-the-nodes-of-a-json-object-tree-with-javascript?answertab=modifieddesc#tab-top
 */
class Traverse {
  /**
   * @constructor
   * @param {object} - object 検索対象のオブジェクト
   */
  constructor(object) {
    this.object = object;
  }
  /**
   * @param {object} - object
   * @reuturn {Map}
   * ToDo:中身正直完全には理解していません
   */
  * traverse(object) {
    const memory = new Set();
    function* innerTraversal(object, path = []) {
      if (memory.has(object)) {
        // we've seen this object before don't iterate it
        return;
      }
      // add the new object to our memory.
      memory.add(object);
      for (const i of Object.keys(object)) {
        const itemPath = path.concat(i);
        yield [i, object[i], itemPath, object];
        if (object[i] !== null && typeof (object[i]) == "object") {
          //going one step down in the object tree!!
          yield* innerTraversal(object[i], itemPath);
        }
      }
    }
    yield* innerTraversal(object);
  }
  /**
   * コンストラクタにセットしたオブジェクトの中に key が存在すれば、その中の value を返すメソッド
   * @param {string} - targetKey
   */
  find(targetKey) {
    let result = []
    for (let [key, value, path, parent] of this.traverse(this.object)) {
      // do something here with each key and value
      if (targetKey === key) {
        result.push(value)
      }
    }
    return new Traverse(result)
  }

  /**
   * インスタンスの値を返すメソッド
   * @param {number} index デフォルトは0
   * @return {any} - value
   */
  getValue(index = 0) {
    const value = this.object[index]
    return value
  }

使い方

function testTraverse() {
  //調べたいオブジェクト
  var object = {
    foo: "fooの中身",
    arr: [1, 2, 3],
    subo: {
      foo2: "sudo.foo2の中身",
      foo: "sudo.fooの中身"
    },
    human: {
      foo: {
        foo: {
          foo: "ネストしても大丈夫",
          name: "スズキ"
        }
      }
    }
  };
  //オブジェクトの中に自分自身を参照するような形のオブジェクトでも無限ループを起こしません。
  object.object = object;

  console.log(object);

  const traverse = new Traverse(object)

  console.log(traverse.find("object"))
  //無限ループは起きない
  //->
  // { foo: 'fooの中身',
  // arr: [ 1, 2, 3 ],
  // subo: { foo2: 'sudo.foo2の中身', foo: 'sudo.fooの中身' },
  // human: { foo: { foo: [Object] } },
  // object: [Circular] }

  console.log(traverse.find("foo").object)
  //-> fooは複数存在するので、該当するものが配列で戻ってきます。
  // [ 'fooの中身',
  //   'sudo.fooの中身',
  //   { foo: { foo: 'ネストしても大丈夫', name: 'スズキ' } },
  //   { foo: 'ネストしても大丈夫', name: 'スズキ' },
  //   'ネストしても大丈夫' ]

  console.log(traverse.find("foo").find("name").getValue())
  //メソッドチェーンでつなげることも可能です。foo のあとにkeyがnameのものがあるものを探します。
  //-> スズキ

  console.log(traverse.find("hogehogehoge").find("name").getValue())
  //存在しない keyを渡すとundifindが返ります。
  //-> undifind
}

元ネタはstack overflow

コードの参考元はstack overflowで調べたところ見つけました。

正直処理の中身は50%くらいしか理解できていませんが、入力と出力だけいじくって上記のコードを作成しました。お役に立てたら幸いです。

リンク

元ネタ:Traverse all the Nodes of a JSON Object Tree with JavaScript

GitHub:Traverse