GASで永続化する方法まとめ（設定や処理結果を保存・読み込みしたい時）

## はじめに
GoogleAppsScriptで、前回呼び出した時の処理結果を再び利用したいような場合があります。
そのためには、前回の処理結果を外部に保存する処理（**永続化**）が必要です。
本記事では、GASで永続化を行うための方法を、コードの例とともにまとめます。

## シチュエーション例
GASで何らかの事象の発生件数を集計したいという状況を考えます。
今日の発生件数と前日までの発生件数を合計した値を、Loggerに出力することにします。
このために、前日までの発生件数を永続化する必要が生まれました。
下記コードにおける、`loadCount` 関数および `saveCount` 関数です。

次節からは、この2つの関数を実装する具体的な方法を考えます。

```js:main.gs
function main() {
  var count_before = loadCount(); // 前日までの発生件数を読み込み
  var count_today = getCountToday();
  var count_sum = count_before + count_today;
  
  Logger.log(count_sum);
  saveCount(count_sum); // 今日までの発生件数を保存
}

function getCountToday() {
  // 今日の発生件数を取得 今回は仮に乱数で決める
  return Math.floor(Math.random() * 10);
}

function loadCount() {
  // 何らかの方法で永続化したデータを読み込みたい
  return count;
}

function saveCount(count) {
  // 何らかの方法でデータを永続化したい
}
```

## 方法1. Properties Service を利用
* [Properties Service](https://developers.google.com/apps-script/guides/properties)は、GASのAPIとして用意されている、簡単にデータを永続化する仕組み
* key, value は全て `string` として保存される
* データのスコープは、下記3種類を選択可能：
    * 1つのスクリプト内
    * 1人のユーザー内
    * 1つのドキュメント内（[Add-on](https://developers.google.com/apps-script/add-ons/)が導入されているGoogleドキュメント）

### [コード例](https://gist.github.com/tmokmss/46412fda002bc3ccad3495b8b74207b2#file-2-propertiesservice-gs)
```js
var properties = PropertiesService.getScriptProperties();
//var properties = PropertiesService.getUserProperties();
//var properties = PropertiesService.getDocumentProperties();

function loadCount() {
  var count = properties.getProperty("Count");
  if (!count) return 0;
  return parseInt(count, 10);
}

function saveCount(count) {
  properties.setProperty("Count", count);
}
```

* 説明
    * まず `PropertiesService.getScriptProperties` 関数で、`Properties` クラスのインスタンスを取得する
        * コードに示したように関数を変えることで、データのスコープを変更することができる
    * 値の読み込みは、`Properties.getProperty(key)` 関数などを使用できる
        * 値は全て `string` で返戻される
        * キーが存在しない場合は `null` が返戻される

    * 値の保存は、`Properties.setProperty(key, value)` 関数などを使用できる
* `Properties` クラスのリファレンスは[こちら](https://developers.google.com/apps-script/reference/properties/properties)


## 方法2. スプレッドシートを利用
* GASのAPIでスプレッドシートのデータを読み書きすることで、永続化する方法
* 具体的には：
    1. スプレッドシートA列にキー・B列に値を入力しておく
    2. それをGASの実行開始時に読み込んで辞書として格納する
    3. また、GAS終了時に辞書を再度スプレッドシートに書き戻す
* 方法1と同じく、キー・値ともに `string` で保存することになる
    * そのため、一度基盤となるコードを用意してしまえば、ユーザー側は方法1と同じように利用可能
* [スプレッドシートの例](https://docs.google.com/spreadsheets/d/1GR1akx335mitzJ8WbbqcpfIdyinJ8cI4LMr_ll2_p6s/edit?usp=sharing)
    * 保存されているキー・値を表で確認できるので、その点ではProperties serviceよりも便利
    * また、スプレッドシートはセルごとに権限を指定できるので、複数人で設定ファイルを共有する際も役立つ


### [コード例]((https://gist.github.com/tmokmss/46412fda002bc3ccad3495b8b74207b2#file-3-spreadsheet-gs))
```js:抜粋版
var settings = readSettings();

function readSettings() {
  var key = fetchListColumn("Settings", "B");
  var value = fetchListColumn("Settings", "C");
  var settings = {};
  for (var i=0; i<key.length; i++) {
    settings[key[i]] = value[i];
  }
  return settings;
}

function writeSettings(settings) {
  var keys = Object.keys(settings);
  var num = keys.length;
  var values = Array(num);
  for (var i=0; i<num;i++){
    values[i] = settings[keys[i]];
  }
  var content = [keys, values];
  content = transpose(content);
  
  var range = Utilities.formatString("B1:C%d", num);
  var sheet = ss.getSheetByName("Settings");
  sheet.getRange(range).setValues(content);
}

function loadCount() {
  var value = settings["Count"];
  if (!value) return 0;
  return parseInt(value, 10);
}

function saveCount(count) {
  settings["Count"] = count;
  writeSettings(settings);
}
```

* 説明
    * 実行開始時にスプレッドシートからデータを取得し、key-valueペアを `settings` 辞書に格納する
    * 以後はそれを読み書きすることで、永続化されたデータにアクセスすることが可能
* スプレッドシートの扱い周りのコードはやや長いので、[Gistを参照](https://gist.github.com/tmokmss/46412fda002bc3ccad3495b8b74207b2#file-3-spreadsheet-gs)
* `writeSettings` 関数はスプレッドシートにアクセスするという少し重い処理
    * 従って、GAS終了時に1回だけ呼び出すという形も良い

## 方法3. Google Driveを利用
* Google Driveにjsonを保存して、それを読み書きする方法
* `JSON.parse` を使って値を読み込むので、数値は自動で適当な型に変換されて読み込まれる
    * このため、`parseInt` などの処理が不要になる
* ファイルとして保存されるので、ドライブをローカルと同期すれば、さらにgitなどで管理することも可能
 
### [コード例]((https://gist.github.com/tmokmss/46412fda002bc3ccad3495b8b74207b2#file-4-drive-gs))
※注意：下記コードは既にドライブに `sample.json` というファイルが存在した場合、不可逆に上書きします

```js:抜粋版
var jsonName = "sample.json";
var settings = readJson(jsonName);

function readJson() {
  var file = getFile(jsonName);
  var content = file.getBlob().getDataAsString();
  if (!content) return {};
  var json = JSON.parse(content);
  return json;
}

function writeJson() {
  var content = JSON.stringify(settings);
  var file = getFile(jsonName);
  file.setContent(content);
}

function loadCount() {
  var count = settings["Count"];
  if (!count) return 0;
  return count;
}

function saveCount(count) {
  settings["Count"] = count;
  writeJson();
}
```

* 説明
    * 実行開始時にドライブからjsonファイルを取得し、key-valueペアを `settings` 辞書に格納する
    * 以後はそれを読み書きすることで、永続化されたデータにアクセスすることが可能
* ドライブ周りのコードは[Gistを参照](https://gist.github.com/tmokmss/46412fda002bc3ccad3495b8b74207b2#file-4-drive-gs)
* こちらについても方法2と同じく、`writeJson` 関数はドライブにアクセスするので、GAS終了時に1回だけ呼び出すという形も良い


## まとめ
* GASで永続化する方法について、3つ紹介しました
* 各特徴を下表にまとめます
* それぞれメリット・デメリットを考慮して、ケースバイケースで使い分けると良いでしょう

|                    | 手軽さ | 確認しやすさ | 管理しやすさ |
|--------------------|--------|--------------|--------------|
| Properties Service | ○      | ×            | △            |
| Spread sheet       | △      | ○            | ○            |
| Drive              | △      | △            | ○            |