見出し画像

複雑階層に必要な値が入り組んだJSONデータから必要な値を抜き出して二次元配列を作りたい

今日も元気にノンプロ研でGAS(Google Apps Script)勉強中です。

ノンプロ研とは、「ノンプログラマーのためのスキルアップ研究会」の略称で、ノンプログラマー(プログラミングを本職にしない人)たちが集まって、プログラミングを武器にすべくスキルを磨いているコミュニティです。

最近、freee API講座を受講してAPIでデータを取得したりできるようになりました。

今回は別のサービスのAPIから取得してきたJSONデータを二次元配列にしてスプレッドシートに転記するというのやってハマったポイントをご紹介。

階層をまたいだデータを二次元配列にしたい

今回はPOSレジアプリの商品データに関してですが、まず商品情報オブジェクトを複数格納した配列があります。

const items = [商品1, 商品2 …];

この商品情報オブジェクトのitem_dataプロパティに格納されたオブジェクトのさらにnameプロパティに商品名が格納されています。

さらに商品にはバリエーションがあり(S・M・Lなど)、このバリエーションごとにシステムIDと商品コードが存在します。このバリエーション情報は、item_dataプロパティのvariationsプロパティの配列内にまたオブジェクトとして複数格納されています。

itemsに格納されている各商品情報オブジェクトをitem、バリエーション情報オブジェクトをvariationとすると以下のような関係です。

  • 商品名:items = [item …] > item_data > name

  • システムID:items = [item …] > item_data > variations = [variation …] > item_variation_data > item_id

  • 商品コード:items = [item …] > item_data > variations = [variation …] > item_variation_data > sku

ほしい二次元配列は、[['システムID', '商品コード', '商品名'] … ] みたいな感じです。

素直にfor of文のネスト

最初は素直にfor of文のネストで書きました。

  const response = UrlFetchApp.fetch(url, params).getContentText();
  const obj = JSON.parse(response); //JSONオブジェクト
  const items = obj.items; //各商品情報が格納された配列

  const aryA = [];
  for (const item of items) {
    const item_name = item.item_data.name;
    for (const variation of item.item_data.variations) {
      const { item_id, sku } = variation.item_variation_data;
      aryA.push([item_id, sku, item_name]);
    }
  }

aryAという空の配列を用意して、そこに値を順番に追加していく感じですね。

まあこれで良いのですが、Arrayオブジェクトの反復メソッドを使ったらどうなるのかを考えてハマりました。

mapメソッドのネスト

そもそもfor of文のネストでできているのに、なぜArrayオブジェクトの反復メソッドを利用したほうが良いのか?というのはまだ勉強中の身で腑に落ちきってないのですが、現時点での理解としては…

  • 配列に対しての処理であることが明確

  • 使用するメソッドの種類によって戻り値のデータ型が明確

という2点です。

このルールに従うと、今回は戻り値として配列がほしいのでfilterメソッドやmapメソッドが候補にあがります。

  const aryB = items.map(item => {
    const item_name = item.item_data.name;
    const data = item.item_data.variations.map(variation => [variation.item_variation_data.item_id, variation.item_variation_data.sku, item_name]);
    return data;
  }).flat();

ということで書いたコードはこちら。階層が違うところから値を取得して、最後に結合させた新しい配列を作りたいので、mapメソッドをネストしています。

ところが、これだと、戻り値に指定した配列 > 子階層のmapで生成される配列 > 親階層のmapで生成される配列 と三次元配列になってしまいます。

今回、スプレッドシート書き込み用にほしいのは二次元配列なので、最後にflatメソッドで配列の次元の深さを減らしています。

reduceメソッドを使ってみた

もう1つ考えたのはreduceメソッドの利用です。

reduceメソッドは、配列を要素ごとに処理を実行し、それぞれの段階で、前の段階の結果と現在の配列の値を用いた処理を行い、それを一つの結果にまとめたものを戻り値に返します。

  const aryC = items.reduce((ary, item) => {
    const item_name = item.item_data.name;
    const data = item.item_data.variations.map(variation => [variation.item_variation_data.item_id, variation.item_variation_data.sku, item_name]);
    return ary.concat(data);
  }, []);

データの格納された配列をひとつひとつ生成し、それを前の段階までの結果の配列とconcatメソッドを使って結合することで、意図した二次元配列を作成しています。

ただ、初期値に空の配列を指定してあげないと、配列内の1番目の値が、1巡目の初期値になってしまい、今回のケースだと商品情報のオブジェクトがそのまま結果に加えられてしまいます。

初期値の指定にヘッダー項目(見出し行)を指定したいときは、便利かもしれません。

ただ、先程の反復メソッドの使用の意図である「使用するメソッドの種類によって戻り値のデータ型が明確」が、今回のケースだと若干曖昧になってしまうので、まだ少しモヤモヤします。

flatMapメソッド

もやもやしつつ、「map flat ネスト」みたいなキーワードでググっていたところflatMapというメソッドがあることを知りました。

flatMap メソッドは、map の後に深さ 1 の flat を呼び出すのと同じです。

MDN Web Docs

これはまさにもやもやを解消するメソッドでは!となり、最終的には以下のコードに落ち着きました。

  const aryD = items.flatMap(item => {
    const item_name = item.item_data.name;
    const data = item.item_data.variations.map(variation => [variation.item_variation_data.item_id, variation.item_variation_data.sku, item_name]);
    return data;
  });

flatMapを用いることで戻り値が配列であることが明確になってますし、同時に配列の深さを調整していることも明確です。

mapメソッドのネストの最後にflat()と書き加えるよりも可読性も高いかなと思いました。

ということでスッキリ。

この記事が気に入ったらサポートをしてみませんか?