【スプレッドシート×GAS】テーブルにしたシートをオブジェクトとして扱う

こんにちは、野村です。

最近ズボンの上に肉が乗ってきました。

木下さんみたいにならないように気をつけます。

あとこの間、映画「ドラえもんの絵世界物語」を観たのですが、バックトゥザフューチャーのような伏線回収の仕方が面白かったです。

そういうの好きな方は是非観てみてください。

課題

スプレッドシートは簡易的なデータテーブルを作成するのに使えますが、それをGASで操作したい場合は二次元配列として取得することになります。

例えば↓のようなテーブルにしたシートがあったとして

GAS上で二次元配列のまま扱おうとすると↓のようになります。

function bad() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const shUsers = ss.getSheetByName("user");
  const usersRecord = shUsers.getDataRange().getValues().slice(1);
  
  usersRecord.forEach(userRecord => {
    console.log(`ID:${userRecord[0]}, 名前:${userRecord[1]} のメールアドレスは ${userRecord[2]} です`);
  });
}

これでも動くことには動きます。

しかし、可読性が低く、開発者にとって優しくないです。

具体的な問題点として、userRecord[0]やuserRecord[1]が何を指しているのかわからない、といったことが挙げられます。

解決策

可読性を高くするには、例えば↓のようにオブジェクト形式で情報を取得できたら良いですよね。

const users = [
  {id: 1, name: "hoge", email: "hoge@example.com"},
  {id: 2, name: "huga", email: "huga@example.com"},
  // ...
];

users.forEach(user => {
  console.log(`ID:${user.id}, 名前:${user.name} のメールアドレスは ${user.email} です`);
});

では↑のようにオブジェクト形式で情報を取得するためにはどうすれば良いでしょうか。

↓のgetObjects関数をコピってください。

function getObjects(sh) {
  const values = sh.getDataRange().getValues();
  const fields = values.shift();
  const records = values;

  const objects = records.map((record) => {
    const object = {};
    record.forEach((value, i) => {
      object[fields[i]] = value;
    });
    return object;
  });
  return objects;
}

このgetObjects関数は引数にテーブル形式になっているシートを渡すと、フィールド名をキーにしたオブジェクトが配列で返ってくるようになってます。

↓使用例

function good() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const shUsers = ss.getSheetByName("user");
  const users = getObjects(shUsers);

  users.forEach(user => {
    console.log(`ID:${user.id}, 名前:${user.name} のメールアドレスは ${user.email} です`);
  });
}

これで可読性爆上げです。

+α

まだこれだけだと、開発中の入力補完が効かないので、開発体験は悪いです。

具体的には、「user」の後に「.」を入力した時に候補(id, name, email)が表示されない、ということが挙げられます。

そんな時はJSDocを使ってオブジェクト定義をしましょう。

↓がJSDocでUserオブジェクトを定義したものになります。

/**
 * @typedef {object} User
 * @property {number} id
 * @property {string} name
 * @property {string} email
 */

定義したUserオブジェクトをusers変数に適用しましょう↓

  /** @type {User[]} */
  const users = getObjects(shUsers);

@typeは直後の変数に適用されます。

User[]と書くことでUserオブジェクトが配列に格納されている、という認識を持たせることができます。

こうすることで「user」の後に「.」を入力した時に候補(id, name, email)が表示されるようになり、開発体験が向上します。

コード全文

/**
 * @typedef {object} User
 * @property {number} id
 * @property {string} name
 * @property {string} email
 */

function good() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const shUsers = ss.getSheetByName("user");
  /** @type {User[]} */
  const users = getObjects(shUsers);

  users.forEach(user => {
    console.log(`ID:${user.id}, 名前:${user.name} のメールアドレスは ${user.email} です`);
  });
}

function getObjects(sh) {
  const values = sh.getDataRange().getValues();
  const fields = values.shift();
  const records = values;

  const objects = records.map((record) => {
    const object = {};
    record.forEach((value, i) => {
      object[fields[i]] = value;
    });
    return object;
  });
  return objects;
}
野村 航平

高専でC・Java、プログラミングスクールにてRuby・JavaScript・SQL・AWSなどを学習後、現在のヒカリシステムに入社。
木下さんの弟子をやらせてもらっています。

関連記事