7
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

GASでスプレッドシートを扱うときにヘッダーとデータをうまく使いたい

Last updated at Posted at 2020-06-18

これは何?

スプレッドシートをGASで扱う時に「ヘッダーとデータ」になってることって多いじゃないですか。

例えばこんな感じ。

data.png

(データ生成には疑似個人情報データ生成サービス を利用しています)

これをGASで扱うときのコードについていくつかパターンを考えてみます。
例えば1人の「名前」を取得する時に

// 二次元配列のまま扱う
const name = rowData[1][1];

// オブジェクトにして扱う
const name = member.name;

のどっちのパターンもありだと思いますが、それのサンプルを載せます。

例として「scoreが90点以上の人の名前を出力する」をやります。

想定している出力結果

碓井幸征
川口亜依
宇佐美当麻
飯野浩二

サンプル1-1: 二次元配列のまま扱う

function sample1_1() {
  const values = SpreadsheetApp.getActive().getActiveSheet().getDataRange().getValues();
  
  // ヘッダを外す
  values.shift();
  
  for (const rowData of values) {
    if(rowData[2] >= 90){
      console.log(rowData[1]);
    }
  }
}

構造はとてもシンプル。
なのですが、このコードを見た人は、コードだけじゃなく、スプレッドシートも見に行って rowData[2]score で、rowData[1]name なのか、っていう確認が必要になると思います。

サンプル1-2: インデックスに名前をつける

function sample1_2() {
  const values = SpreadsheetApp.getActive().getActiveSheet().getDataRange().getValues();
  values.shift(); // ヘッダを外す
  
  // インデックスを変数にしちゃう
  const name  = 1;
  const score = 2;
  
  for (const rowData of values) {
    if(rowData[score] >= 90){
      console.log(rowData[name]);
    }
  }
}

こうすることで rowData[score] が何を示すのかわかりやすくなりました。
気になるところとしては

  const name  = 1;
  const score = 2;

で、インデックスを指定しているのが気になりません?

サンプル1-3: 分割代入を使う

(2020/06/22 追記)

function sample1_3() {
  const values = SpreadsheetApp.getActive().getActiveSheet().getDataRange().getValues();

  // ヘッダを外す
  values.shift();

  for (const rowData of values) {
    // ここでヘッダを指定する
    const [id, name, score] = rowData;
    
    if(score >= 90){
      console.log(name);
    }
  }
}

ヘッダの位置がわかってるのであればこういうやりかたもあるのですね。。。


サンプル2-1: オブジェクトにする

function sample2_1() {
  const values = SpreadsheetApp.getActive().getActiveSheet().getDataRange().getValues();
  values.shift(); // ヘッダを外す
  
  const id    = 0;
  const name  = 1;
  const score = 2;
  
  // 二次元配列を、オブジェクトが入った一次元配列に変換する
  const members = [];
  for (const rowData of values) {
    const member = {
      id   : rowData[id],
      name : rowData[name],
      score: rowData[score]
    }
    members.push(member);
  }
  
  for (const member of members) {
    if (member.score >= 90 ) {
      console.log(member.name);
    }
  }
}

「変換」が必要になりますが、データが多くなれば多くなるほど、それ以降で扱いやすくなります。
今は C列 までしか無いので rowData[2] とかでも把握できますが、私が今日実務で相手にしたスプレッドシートでは AL列 まであって、rowData[37] とか、なんのこっちゃって。

あ、もちろん必要な列のデータだけ使えばいいんですよ。37列全てのindexを確保する必要はないです。

... でも、AL列が必要なら

  const xxx = 37;

が必要になるわけですよね。なんかうまいことできないものか。

そして、ここまでのサンプルは シートに列が追加・削除・変更・移動されたらコードも修正する必要がある わけです。

ちょっと考える。ヘッダーをキーにしたい。

ここで気づくわけです。

    const member = {
      id   : rowData[id],
      name : rowData[name],
      score: rowData[score]
    }

このオブジェクトの key になってるのって、スプレッドシートの ヘッダー だよなって。
さらに、ヘッダーについては

  values.shift(); // ヘッダを外す

ってしてるけど、これって ↓こうやったらヘッダだけ取り出せるよね。

  const header = values.shift();

...ここからの理解にだいぶ時間がかかったのですが、自分なりに整理すると、

// ヘッダー部
const header = ["id", "name", "score"];

// データ部
const values = [
  [1, "古河結", 89],
  [2, "倉橋彩夏", 69],
  [3, "浅見俊文", 67],
...
];

↑という2つの配列があって、この2つをこうしたい↓

const members = [
  { id: 1, name: "古河結",  score: 89 },
  { id: 2, name: "倉橋彩夏", score: 69 },
  { id: 3, name: "浅見俊文", score: 67 }
];

headervalues の「列数」(という言い方でいいのか?)は一致してることが前提。

ポイントは header の要素を、オブジェクトのキーにしたい。

この時点で私の頭の中はこうなってます。

001.png

002.png

003.png

004.png

005.png

ではこれをコードにするとどうなるか。

サンプル2-2: ヘッダーをキーにしたオブジェクトにする


function sample2_2() {
  const values = SpreadsheetApp.getActive().getActiveSheet().getDataRange().getValues();
  const header = values.shift();
  
  const members = [];
  for (const rowData of values) {
    // カラのオブジェクトを用意して
    const member = {};
    header.forEach( (title, index) => {
      // ヘッダの値(title) をkeyにしてオブジェクトに追加する
      member[title] = rowData[index]
    });
    
    members.push(member);
  }

  for (const member of members) {
    if (member.score >= 90 ) {
      console.log(member.name);
    }
  }
}

valuesから1行分のデータを取得してrowDataにして
member[title] = rowData[index]
を作っていくという発想。

const member = {};
member['id'] = 1;
member['name'] = '古河結';
member['score'] = 89;

ってやると、memberがこうなる↓

member = { id: 1, name: "古河結",  score: 89 }

ので、そのあと

members.push(member);

こうしてあげる。(のを valuesの要素数分だけ繰り返す)

...どうかなー。これ、わかりやすいのかな。もっといい方法があるんじゃないかなー。
って思ってた。

サンプル3(最終形態?): Object.assignを使う

こんな技があるってよ!!!

function sample3_1() {
  const values = SpreadsheetApp.getActive().getActiveSheet().getDataRange().getValues();
  const header = values.shift();
  
  const members = values.map(rowData => {
    return Object.assign(...header.map((title,i) => {
      return {[title]: rowData[i]};
    }));
  });

  for (const member of members) {
    if (member.score >= 90 ) {
      console.log(member.name);
    }
  }
}

↓ ここがナゾい。

  const members = values.map(rowData => {
    return Object.assign(...header.map((title,i) => {
      return {[title]: rowData[i]};
    }));
  });

こゆときは分解する。

分解1

header.map を見てみる。

header.map((title,i) => {
  return {[title]: rowData[i]};
})

の動作を確認する。

function sample3_2() {
  const header = ["id", "name", "score"];
  const rowData = [1, "古河結", 89];

  const result1 = header.map((title,i) => {
    return {[title]: rowData[i]};
  });
  
  console.log(result1); // =>  [ { id: 1 }, { name: '古河結' }, { score: 89 } ]
}

こ、、、これはもしかしたら

003.png

じゃないか!!!

でもよく見たら違うものだ。惜しい

// ほしいもの
[ { id: 1, name: '古河結', score: 89 } ]

// 上記の結果
[ { id: 1 }, { name: '古河結' }, { score: 89 } ]

だとしても、ほしいものにだいぶ近づいてきた。

↓コードに私の解釈をいれるとこういうことか。

function sample3_2() {
  const header = ["id", "name", "score"];
  const rowData = [1, "古河結", 89];

  // header から要素を1つ取り出してtitleに入れる。その要素のインデックスを i とする。
  const result1 = header.map((title,i) => { 
    // title をキーにして、`rowData[i]` を値に持つオブジェクトを作る。
    return {[title]: rowData[i]}; //result1 は ←これ1つを1つの要素とする配列になる (mapの仕様)
  });
  
  console.log(result1); // =>  [ { id: 1 }, { name: '古河結' }, { score: 89 } ]
}

mapの仕様はこちら。

図にするとこうか。
006.png

これで result1 の中身は [ { id: 1 }, { name: '古河結' }, { score: 89 } ] になってることがわかった。

分解2

続いて

Object.assign(...header.map((title,i) => {
  return {[title]: rowData[i]};
}));

を見ていくが、すでに分解1で

header.map((title,i) => {
  return {[title]: rowData[i]};
})

の結果はわかってるので置き換えると、つまり

Object.assign(...[ { id: 1 }, { name: '古河結' }, { score: 89 } ]);

が何なのかわかればいい。

実験してみよう。

function sample3_3() {
  const result1 = [ { id: 1 }, { name: '古河結' }, { score: 89 } ];
  const result2 = Object.assign(...result1);
  console.log(result2); // => { id: 1, name: '古河結', score: 89 }
}

ここで、ほしいものの形になってる。すごい。

わかんないのは Object.assign... の2点。

まず ... から。

... ってなんだよ!そう、スプレッド構文 - JavaScript | MDN だ!

↑ ここのサンプルコードの通り、「配列を展開してくれる君」のようだ。
これはつまり ...numbers って書くと numbers 配列を「展開」してくれるのでスプレッド構文ね。

  const result1 = [ { id: 1 }, { name: '古河結' }, { score: 89 } ];

  const result2 = Object.assign(...result1);
  // ↑これと ↓ これは同じこと
  const result2 = Object.assign({ id: 1 }, { name: '古河結' }, { score: 89 });

続いて Object.assign

Object.assign() - JavaScript | MDN

↑ここのサンプルをみるとオブジェクトをマージしてくれるようだ。

結果的に

{ id: 1 }, { name: '古河結' }, { score: 89 }

{ id: 1, name: '古河結', score: 89 }

になる、と。便利。

図にするとこんな感じか。

007.png

分解3

いよいよ最終形態。

  const members = values.map(rowData => {
    return Object.assign(...header.map((title,i) => {
      return {[title]: rowData[i]};
    }));
  });

これまでやったことを合体させると

function sample3_4() {
  const header = ["id", "name", "score"];
  const values = [
    [1, "古河結", 89],
    [2, "倉橋彩夏", 69],
    [3, "浅見俊文", 67]
  ];
  
  // valuesの要素数分繰り返すよ
  const members = values.map(rowData => {
    // ["id", "name", "score"](header) と [1, "古河結", 89](rowData) から
    // { id: 1, name: '古河結', score: 89 } を作って members に入れていくよ
  });

 console.log(members);
}

/* ログ
[
  { id: 1, name: '古河結', score: 89 },
  { id: 2, name: '倉橋彩夏', score: 69 },
  { id: 3, name: '浅見俊文', score: 67 }
]
*/

... わかったような気になった。
もう少し慣れる必要があるな。

まとめ

必ずしも「最終形態」(サンプル3)がいいわけではなく、スプレッドシートの大きさ、コードの規模、このコードをメンテナンスする人の理解度...などによって「関わる人にとってやりやすい書き方」ができればいいと思います。(「高度」である必要はない)

そして、「最終形態」と書いてますが、実はもっとステキな書き方があるかもしれないので、ご存知でしたらぜひコメント欄に!

7
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?