[GAS]二次元配列からオブジェクト/MAPオブジェクトに変換する (MapとReduceで)


### 想定している状況
* スプレッドシートのデータをGASで取得して処理したい
* ただしフォーマットが確定していない
* あとからカラムの追加や移動があるかもしれない

A「1列目ID、2列目TYPE、3列目DATE…やな！」
B「ごめん、IDのとなりにNAMEが必要やったわ！」
A「できたで。1列目ID、2列目NAME、3列目TYPE、4列目DATE…やな！」
B「先日のNAMEやけどな、FAMILY NAMEとLAST NAMEに分けたいねん！」
A「…。」

列番号指定でスクリプトを動かすのはやめて、オブジェクト化してカラム名で扱いましょう！

```` Javascript
console.log(array[1]) //なにが入っているかわからない、列が移動すると困る
console.log(object["name"]) //わかる
````


# 目次
[ダミーデータ](#ダミーデータ)
[二次元配列を取得する](#二次元配列)
[オブジェクトにする](#オブジェクト)
[MAPオブジェクトにする](#mapオブジェクト)
[おまけ…Python(gspread)の場合](#おまけpythongspread)

# ダミーデータ
ダミーデータを用意しました。１行目がヘッダー、２行目以降にレコードが続きます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661777/d3b36f4c-bd7d-576b-4688-7094d32843cc.png" width="50%">

https://hogehoge.tk/personal/generator/

# 二次元配列
まずは`getValues();`で二次元配列を取得します。

``` Javascript
  const id = "XXXXXXXXXXXXXXXX";
  const ss = SpreadsheetApp.openById(id);
  const sheet = ss.getSheetByName('DUMMY_personal_infomation');

  // 二次元配列を取得する
  const values = sheet.getDataRange().getValues();

  console.log(values)
```
![スクリーンショット 2022-05-25 11.02.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661777/79e62ae6-d4a3-2506-abae-6a4c3b459ce5.png)


# オブジェクト
二次元配列をオブジェクトにしてみましょう。
``` Javascript
  // 二次元配列を取得する
  const values = sheet.getDataRange().getValues();

  // オブジェクトにする
  const [header, ...records] = values;  //１行目がヘッダー、以降がレコード
  const objects = records.map(
    record => record.reduce((acc, value, index) => {
      acc[header[index]] = value;
      return acc;
    }, {})
  );

  console.log(objects)
```
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661777/72d14138-63bd-682b-dbae-c3d7fc4e75fd.png">

* 二次元配列のvalues[0]がヘッダー、[1]以降がレコードにあたります。
* `values.shift();`で最初の要素を削除してもよいですが、`…`スプレッド構文でも最初の要素(header)と、以降の要素(records)に分けられます。
* あとは行数recordsの数だけループして処理していけばよいでしょう。
* reduceのaccは「直前の処理で返された値」を持つので、初期値「 {}」に対して順々にプロパティを追加していきます。


なお、プロパティの追加にスプレッド構文を使うとさらに短くなります。

``` Javascript
  const values = sheet.getDataRange().getValues();

  const [header, ...records] = values;
  const objects = records.map(
      record => record.reduce((acc, value, index) => ({...acc, [header[index]]:value}), {})
    );  
```

# MAPオブジェクト
二次元配列をMAPオブジェクトにしましょう。

``` Javascript
  // 二次元配列を取得する
  const values = sheet.getDataRange().getValues();

  // MAPオブジェクトにする
  const [header, ...records] = values;
  const maps = records.map(
    record => record.reduce((acc, value, index) => acc.set(header[index], value), new Map())
  );

  for (m of maps) {
    console.log(...m);
  }
```
![スクリーンショット 2022-05-25 11.16.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661777/4f8a703d-697f-e2a6-32a5-36f66eadd9c9.png)

* Mapオブジェクトもオブジェクトと同様にキーと値のペアを保持します。
* Arrayで使用されるMapメソッドとは異なるため注意。ややこしいですね。
* またMapオブジェクトは、要素を追加したり取得したりするメソッドが用意されています。
* `new Map();`で空のMAPオブジェクトを生成して、初期値としてreduceに渡します。
* `set(key, value);`で要素を追加します。

オブジェクトでスプレッド構文を駆使するより、専用メソッドのあるMAPオブジェクトの方が処理がわかりやすい…でしょうか？

# おまけ…Python
Pythonの辞書型で取得するには、gspreadライブラリを使用して`worksheet.get_all_records()`の１行でよい…

``` Python
from google.colab import auth
from google.auth import default
import gspread
from pprint import pprint #Dictを整形してきれいに出力できる

# 認証
auth.authenticate_user()
creds, _ = default()
gc = gspread.authorize(creds)

workbook = gc.open_by_key('XXXXXXXXXXXXXXXX')
worksheet = workbook.worksheet('DUMMY_personal_infomation')

# worksheetの全データをdictで取得
recordsDict = worksheet.get_all_records() 

pprint(recordsDict)
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2661777/ea878e02-8176-d74c-f4d6-0e1c7758d860.png" width="70%">