こまごまGASいじり ～未入力判定～

お疲れさまです、みやもとです。

今回は前回の記事でちらっと「次はnull判定だ」と書いてた話になります。

https://qiita.com/Mymt_aggw2208/items/9d839072632e483fc370

# 未入力判定がわからない

なんで未入力判定でひっかかったかというと、日記BOTで「今日の日記が記入されているか」という条件判定が必要だったからです。
日記BOTで使ったスプレッドシートは日付・ID・日記本文（日本語）・日記本文（英訳）・アドバイス、という項目に分かれていて、どのメッセージを返すかを入力済み項目によって判定するというロジックを組んでいました。
ただ、未入力判定がうまくいかなかったため最終的にはステータスを管理するコードを項目として追加し、コードの値によって返すメッセージを判定する形に変更した経緯があります。

ということで、こちらがうまく動かなかったコード。

```JavaScript:コード.gs
    let data = getTodaysDiary([dateString,id]);

    if (data == null) {
      // 基準日の日記データが存在しない場合、今日の出来事を質問する
      replyText = yesterday ? '昨日' : '今日';
      replyText = replyText +  'はどんなことがありましたか？';
      // 時間帯によって挨拶を変更
      if (timeString >= '5:00:00' && timeString < '11:00:00') {
        replyText = 'Good morning!\n' + replyText;
      } else if (timeString >= '11:00:00' && timeString < '18:00:00') {
        replyText = 'Good afternoon.\n' + replyText;
      } else {
        replyText = 'Good evening.\n' + replyText;
      }
      // 日記データを作成する
      createDiary([dateString, id, '', '', ''])
      appendLog(id, '日記データを新規作成')
    }
```
書き方とかはともかく、やりたいことはわかりやすいと思うのですがいかかでしょう。
今日の日記行を取得するメソッドを呼びだして、nullだったら今日の日記データはまだないということで処理を進めるという流れです。
ただこれは日記データ取得のメソッドも含めて結局うまくいかず、nullとかundefinedとかいろいろ試したのですがダメでした。

## １回目：lengthを取得してみよう
ということで、ここから検証です。
前回記事でデータ取得はうまくいったので、今回は取得項目の内容がどう返ってくるかを確認していきたいと思います。

使ったテストデータはこちら。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3477580/34017211-1e0a-3cb0-cbd6-105c8cc65f42.png" width="50%">
前回使ったのとほぼ一緒ですが一応今回用に別で作ってます。

まずは日付とIDでデータを絞り込んで、名称と判定用項目の内容とついでに文字列長を見てみたいと思います。
もし未入力＝文字列長0であっさり取れればそれで解決です。コードはこちら。
```JavaScript:コード.gs
function test02() {
  // 2024/06/06のuserId02データを取得する
  var date = new Date('2024/06/06');
  var keyDate = Utilities.formatDate(date, 'JST', 'yyyy/MM/dd');
  var result = getReadTest02(keyDate, 'userId02');
  for (var rs of result) {
    RESULT02.appendRow(['テストデータ：',rs[2]]);
    RESULT02.appendRow(['判定用項目：',rs[3]]);
    RESULT02.appendRow(['テストデータlength：', rs[2].length])
    RESULT02.appendRow(['判定用項目length：', rs[3].length])
  }
}
/**
 * 検証２データを読み込むメソッド
 * @param {String} date - 日付
 * @param {String} id - ユーザーID
 */
function getReadTest02(date, id){
  // 最終行の取得
  let lastRow = TEST02.getLastRow();
  // getRangeでは0を指定することができないのでデータが存在しないことになる
  if(lastRow <= 1) return;
  // データの取得
  let datas = TEST02.getRange(2,1,lastRow-1, 3).getValues();
  // データの検索
  let data = datas.filter((value) =>{
    return Utilities.formatDate(value[0], 'JST', 'yyyy/MM/dd') == date && value[1] == id
  })
  return data;
}
```

で、これを実行してみたところ。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3477580/4585073c-de8e-dadb-201d-18ab1ebd0315.png" width="80%">

<b>これは見事な実行時エラー。</b>まぁそうあっさり解決はしませんわな。
これ、検証シートに出力された内容を見るとわかるのですが、名称項目のlengthは普通に出力されています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3477580/ed6f3651-f87e-0782-5c4b-fa932b28e0fd.png" width="50%">
ということは検証用項目（要は未入力項目）のところで「そんなプロパティあらへんで」とエラーになってる、つまりnullの変数のプロパティ見ようとしてエラーになってるので当たり前といえば当たり前の事象ですね。

当たり前がわかったところで次いきましょう。

## ２回目：isBlankは使えるか？
次はちょっと検索かけたところ、isBlankで判定できるよーと出てきたので早速試してみます。
シート読み込みメソッドは変更ないので省略。

```JavaScript:コード.gs
function test02() {
  // 2024/06/06のuserId02データを取得する
  var date = new Date('2024/06/06');
  var keyDate = Utilities.formatDate(date, 'JST', 'yyyy/MM/dd');
  var result = getReadTest02(keyDate, 'userId02');
  for (var rs of result) {
    RESULT02.appendRow(['テストデータ：',rs[2]]);
    RESULT02.appendRow(['判定用項目：',rs[3]]);
    RESULT02.appendRow(['テストデータisBlank：', rs[2].isBlank()])
    RESULT02.appendRow(['判定用項目isBlank：', rs[3].isBlank()])
  }
}
```

で、さっそく実行。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3477580/e1c5d968-1de5-bc67-4c38-048a84553f73.png" width="80%">
<b>おっと？</b>
「そんな関数あらへんで」というエラーになりました。しかも名称のとこでひっかかってるのでnull云々関係ではないっぽい。
GoogleスプレッドシートのisBlankで調べ直しても一応それに関する情報自体は出てくるので、関数が廃止されたとかでもなかろうし。
セル１つを指定すると範囲と認識されなくなって使えない感じなんでしょうかね。

## ３回目：シンプルに空文字列で判定してみる
ここでいったんアプローチを変えましょう。
日記データは文字列型です。普段プログラム組むときに想定する文字列型で入力されていない状態とはつまり
* null（初期化されていない）
* empty（空）
* '' (0桁文字列)
あたりと考えます。
もしかして0桁文字列との比較で上手いこといったりしない？
多分しないけどダメ元ってやつで
念のため試してみましょう。

```JavaScript:コード.gs
function test02() {
  // 2024/06/06のuserId02データを取得する
  var date = new Date('2024/06/06');
  var keyDate = Utilities.formatDate(date, 'JST', 'yyyy/MM/dd');
  var result = getReadTest02(keyDate, 'userId02');
  for (var rs of result) {
    RESULT02.appendRow(['テストデータ：',rs[2]]);
    RESULT02.appendRow(['判定用項目：',rs[3]]);
    RESULT02.appendRow(['判定用項目0桁文字列：', rs[3] ==''])
  }
}
```
とりあえず実行してみたところ、ひとまず実行時エラーにはなりませんでした。
一歩前進したかもしれないので出力を確かめてみましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3477580/72d24705-b4f8-a8d0-f790-7ed66f2f5da7.png" width="50%">

<b>前進してませんでした。</b>

## ４回目：そろそろ項目自体を調べてみる
ここでようやくあてずっぽうをあきらめて、取得した項目がどういう状態なのかを調べてみることを思いつきました。
Object.prototype.toString.callを使うと変数のタイプを出力してくれるようなので、判定用の項目と念のため名称についてタイプを出力してみましょう。

```JavaScript:コード.gs
function test02() {
  // 2024/06/06のuserId02データを取得する
  var date = new Date('2024/06/06');
  var keyDate = Utilities.formatDate(date, 'JST', 'yyyy/MM/dd');
  var result = getReadTest02(keyDate, 'userId02');
  for (var rs of result) {
    RESULT02.appendRow(['テストデータ：',rs[2]]);
    RESULT02.appendRow(['判定用項目：',rs[3]]);
    RESULT02.appendRow(['テストデータtype：', Object.prototype.toString.call(rs[2])])
    RESULT02.appendRow(['判定用項目type：', Object.prototype.toString.call(rs[3])])
  }
}
```
これで実行した結果、名称がStringなのは予想通りでしたが判定用項目がちょっと違っていました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3477580/c89ba5df-8d14-51f1-8b7d-57ff669a4c5e.png" width="50%">
「Object Undefined」となっています。
日記BOT作成時のnull判定で試行錯誤してた時に「undefined」という単語自体は出てきていたのですが、それで条件付けしてもうまく動かなかったので記憶の隅に押しやったままでした。
そこでもう一度調べ直したところ、データ型確認について書かれた記事が。

https://it.notepad-blog.com/programming/gas/352/

この記事の中で取得したデータ型に対してやはりif文で判定しているのですが、<b>判定は文字列との比較になっています</b>。
私がundefinedで判定した時は「タイプ名が何か」ではなく「対象のセル（＝変数）がundefinedの状態か」という書き方になっていました。
もしかして取得したタイプ名を文字列で判定すればいける？

## ５回目：タイプ名がUndefinedかどうか
思いついたからには試してみましょう。
これでダメだったらもうなんもわからん。

```JavaScript:コード.gs
function test02() {
  // 2024/06/06のuserId02データを取得する
  var date = new Date('2024/06/06');
  var keyDate = Utilities.formatDate(date, 'JST', 'yyyy/MM/dd');
  var result = getReadTest02(keyDate, 'userId02');
  for (var rs of result) {
    RESULT02.appendRow(['テストデータ：',rs[2]]);
    RESULT02.appendRow(['判定用項目：',rs[3]]);
    RESULT02.appendRow(['判定結果型：',Object.prototype.toString.call(rs[3]) == '[object Undefined]'])
  }
}
```

これを実行した結果、検証シートにこんな感じで出力されました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3477580/fabbd3b8-11d3-09ff-14e9-e4acf63d3fb6.png" width="50%">

どうやらちゃんと判定できたようです。