本サイトはアフィリエイト広告(A8.net/もしもアフィリエイト/アクセストレード等)を利用しています
GASでスプレッドシートをJSON APIとして公開する(doGet実装)
📂 Web公開

GASでスプレッドシートをJSON APIとして公開する(doGet実装)

📅 ⏱ 読了 約9分 ✍️ 凛

💡 本記事には広告(アフィリエイトリンク)が含まれる場合があります。

凛です。先日、我が家でつけている「野菜の在庫リスト」をスプレッドシートで管理していたのですが、「このデータを別の自作ページからも読めたら便利なのに」と思う場面がありました。夜勤明けの眠い頭で、コピペで別のシートに貼り付けては「あれ、こっちは古いままだ」と混乱すること数回。データの置き場所は1つにして、あちこちからそれを読みに行く形にしたい、と考えたのがきっかけです。

そこでたどり着いたのが、GAS(Google Apps Script)でスプレッドシートの中身を「JSON API」として公開する方法でした。難しそうな名前ですが、やってみると拍子抜けするほど短いコードで実現できます。この記事では、私が実際に手を動かしながら覚えた手順を、初心者の方にも分かるように順を追って書いていきます。

GASでスプレッドシートをJSON APIとして公開する(doGet実装)

「API」と聞くと身構えてしまいますが、ざっくり言うと「URLにアクセスすると、データが決まった形(今回はJSON)で返ってくる窓口」のことです。GASを使えば、スプレッドシートをそのままこの窓口にできます。ここからは、その窓口の作り方を一歩ずつ見ていきましょう。

そもそもJSON APIって何をするもの?

まずは全体像から。専門用語が続くと眠くなるので、我が家の在庫リストを例に、なるべく普段の言葉でイメージをつかんでいきます。

JSONは「データの決まった書き方」

JSON(ジェイソン)は、データをやりとりするための書き方の一種です。たとえば「トマト、150円」「なす、100円」というデータは、JSONだとこう書きます。

[
  { "name": "トマト", "price": 150 },
  { "name": "なす", "price": 100 }
]

[ ]が「リスト(配列)」、{ }が「1件分のかたまり」、"name": "トマト"が「項目名と値のペア」です。人間にもそこそこ読めますし、機械にとってはとても扱いやすい形になっています。スプレッドシートの表を、この形に変換して返すのが今回のゴールです。

doGetは「URLを開かれたときに動く関数」

GASでWebアプリを作るときの主役が doGet という名前の関数です。これは決まった名前で、「このスクリプトのURLがブラウザなどから開かれた(GETされた)ときに自動で呼ばれる」という特別な役割を持っています。

つまり私たちがやることは、doGet の中に「シートを読んでJSONにして返す」処理を書くだけ。名前は必ず doGet にする必要があるので、そこだけ注意してください。

ContentServiceで「返す中身」を作る

doGet が最後に「これを返してね」と手渡すデータを作るのが ContentService です。文字列を用意して、「これはJSONですよ」という目印(MimeType)を付けて返します。この3点(doGet・JSON・ContentService)が今回の登場人物のすべてです。

最小構成のコードを書いてみる

理屈はこのくらいにして、実際に動くコードを書いてみましょう。まずは「シート全部をJSONで返すだけ」の最小構成からです。

スプレッドシートを用意する

新しいスプレッドシートを1つ作り、1行目に見出し、2行目以降にデータを入れます。今回は「在庫リスト」を想定して、こんな表にしました。

idnamepricestock
1トマト1508
2なす10012
3きゅうり905

このシートを開いた状態で、メニューの「拡張機能」→「Apps Script」を選ぶと、このスプレッドシートに紐づいたスクリプトエディタが開きます。

全件をJSONで返すdoGet

エディタに次のコードを貼り付けます。各行にコメントを付けたので、何をしているか追いながら読んでみてください。

// ※構文・API仕様を確認済み(GAS V8ランタイム向け)

// URLが開かれたときに自動で呼ばれる関数(名前は必ず doGet)
function doGet(e) {
  // このスクリプトに紐づくスプレッドシートを取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  // 先頭のシート(データが入っているシート)を取得
  const sheet = ss.getSheets()[0];
  // データが入っている範囲を丸ごと二次元配列で取得
  const values = sheet.getDataRange().getValues();

  // 1行目(見出し行)をキーとして取り出す
  const headers = values[0];
  // 2行目以降を1件ずつオブジェクトに変換していく
  const data = [];
  for (let i = 1; i < values.length; i++) {
    const row = values[i];        // その行のデータ(配列)
    const obj = {};               // 1件分の入れ物
    for (let j = 0; j < headers.length; j++) {
      // 見出しをキー、そのセルの値を値にして詰める
      obj[headers[j]] = row[j];
    }
    data.push(obj);               // 配列に追加
  }

  // オブジェクト配列をJSON文字列にして、JSONだと明示して返す
  return ContentService
    .createTextOutput(JSON.stringify(data))
    .setMimeType(ContentService.MimeType.JSON);
}

やっていることは3ステップだけです。①シートの中身を二次元配列で読む、②1行目を見出しにして各行をオブジェクトに変換する、③JSON文字列にして返す。これで [{ "id":1, "name":"トマト", ... }, ...] という形が返るようになります。

getDataRangeとgetValuesの役割

getDataRange() は「データが入っている範囲」を自動で判断して返してくれるメソッドです。空行まで含めた全体を取りたいときに便利で、範囲をいちいち A1:D100 のように指定しなくて済みます。

そのあとの getValues() で、その範囲の中身を二次元配列(配列の中に各行の配列が入った形)として取り出します。values[0] が1行目(見出し)、values[1] 以降がデータ行、という対応です。ここがイメージできれば、あとの変換処理もすっと読めるはずです。

デプロイして公開URLを発行する

コードが書けても、公開しないとURLとしては使えません。GASの「デプロイ」という操作で、このスクリプトをWebアプリとして世に出します。手順は現行のUIに沿って説明します。

新しいデプロイの作り方

エディタ右上の青い「デプロイ」ボタンから進めます。

  1. 「デプロイ」→「新しいデプロイ」をクリック
  2. 歯車アイコン(種類の選択)から「ウェブアプリ」を選ぶ
  3. 「次のユーザーとして実行」は通常「自分」でOK
  4. 「アクセスできるユーザー」を選ぶ(後述)
  5. 「デプロイ」を押すと、ウェブアプリのURLが発行される

初回は「アクセスを承認」する画面が出るので、指示に従って自分のGoogleアカウントで許可します。発行されたURL(https://script.google.com/macros/s/~/exec のような形)をブラウザで開くと、先ほどのJSONが表示されるはずです。

更新したのに反映されないときは

ここが初心者が一番つまずくポイントです。コードを直したあと、URLを開いても古い内容のままで「あれ?」となることがあります。これは、デプロイした時点の「バージョン」が固定されているためです。

コードの修正を公開URLに反映するには、「デプロイ」→「デプロイを管理」を開き、対象のデプロイの編集(鉛筆アイコン)から「バージョン」を「新バージョン」にして更新します。この一手間を忘れると、いくらコードを直しても表示が変わらないので覚えておいてください。

アクセス範囲は慎重に

「アクセスできるユーザー」を「全員」にすると、URLを知っている人は誰でもそのJSONを読めます。便利な反面、載せてはいけない情報があります。

  • 個人情報(氏名・住所・電話番号・メールなど)は載せない
  • パスワードやAPIキーなど秘密の値をシートに置かない
  • あくまで「読み取り専用」の窓口として使う(書き込み処理は付けない)

自分だけ・特定の人だけが使うなら、アクセス範囲を「自分のみ」や組織内に絞るのが安心です。公開範囲は「必要最小限」を合言葉にしてください。

クエリパラメータで絞り込む

全件返すだけでも十分使えますが、「idが2の商品だけ欲しい」といった場面もあります。URLの末尾に付ける「クエリパラメータ」を使えば、特定の行だけを返せます。

e.parameterで受け取る

doGet(e)e には、URLで渡された情報が入っています。たとえば ...exec?id=2 のようにアクセスすると、e.parameter.id に文字列の "2" が入ります。これを使って、該当する行だけに絞り込みます。

// ※構文・API仕様を確認済み(GAS V8ランタイム向け)

function doGet(e) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  const values = sheet.getDataRange().getValues();
  const headers = values[0];

  // まず全件をオブジェクト配列に変換(さっきと同じ処理)
  let data = [];
  for (let i = 1; i < values.length; i++) {
    const obj = {};
    for (let j = 0; j < headers.length; j++) {
      obj[headers[j]] = values[i][j];
    }
    data.push(obj);
  }

  // URLに ?id=◯◯ が付いていたら、その行だけに絞り込む
  const id = e.parameter.id;              // 例: "2"(付いていなければ undefined)
  if (id) {
    // シート側の id は数値なので、文字列に揃えて比較する
    data = data.filter(function (row) {
      return String(row.id) === String(id);
    });
  }

  return ContentService
    .createTextOutput(JSON.stringify(data))
    .setMimeType(ContentService.MimeType.JSON);
}

これで ?id=2 を付けると「なす」の1件だけが返り、何も付けなければ全件が返ります。比較のときに String() で両方を文字列にそろえているのがポイントで、これを忘れると「数値の2」と「文字列の”2”」が一致せず、絞り込めなくなります。

複数の条件で絞る発展例

同じ要領で条件を増やせます。たとえば「在庫(stock)が5より多いものだけ」という絞り込みも足せます。

// ※構文・API仕様を確認済み(GAS V8ランタイム向け)

// 在庫が min より多い行だけに絞る(?minStock=6 のように使う)
const minStock = e.parameter.minStock;   // 例: "6"
if (minStock) {
  const n = Number(minStock);            // 数値に変換
  data = data.filter(function (row) {
    return Number(row.stock) > n;        // stock も数値化して比較
  });
}

こうしたパラメータを組み合わせれば、シート1枚から用途に応じた色々なデータを取り出せます。ただし条件を増やしすぎると複雑になるので、まずは1つか2つから始めるのがおすすめです。

つまずきやすいポイントと回避策

私が実際にハマった失敗と、その回避策をまとめておきます。同じところで悩む方が減れば嬉しいです。

文字化け・日本語がおかしくなる

JSONの中の日本語が変な文字になることがあります。多くの場合、setMimeType を付け忘れているのが原因です。ContentService.MimeType.JSON を指定すると、文字コードの扱いが適切になり、日本語もきちんと表示されやすくなります。返す直前の1行を必ず入れてください。

コードを直したのに変わらない

前述のとおり、これは「デプロイのバージョン更新」を忘れているケースがほとんどです。エディタ上で「保存」しただけでは公開URLには反映されません。「デプロイを管理」から新バージョンにする、をセットで覚えましょう。

日付が文字列になって扱いにくい

セルに日付が入っていると、getValues() で取り出したときに「日時オブジェクト」として返り、JSONにすると長い形式の文字列になります。表示を整えたいときは、返す前に自分で見やすい文字列に変換しておくと扱いやすくなります。

つまずきよくある原因回避策
日本語が化けるMimeType未指定setMimeType(ContentService.MimeType.JSON) を付ける
修正が反映されないバージョン未更新「デプロイを管理」で新バージョンに更新
絞り込めない数値と文字列の比較ずれString()Number() で型をそろえる
日付が読みにくい日時が長い文字列になる返す前に整形して文字列化する

ブラウザから直接fetchするとCORSで詰まることがある

自作のWebページからJavaScriptの fetch でこのAPIを直接呼ぼうとすると、環境によっては「CORS」という仕組みに引っかかってうまく読めないことがあります。これはブラウザのセキュリティの都合によるもので、一概に「必ずこうなる」とは言い切れません。私の環境では詰まる場面もありました。

対処としては、次のような方法があります。

  • URLをブラウザで直接開いて、返ってきたJSONをそのまま確認・利用する
  • 別のサーバー(や別のGAS)側からアクセスして、結果を受け渡す
  • 後述するスプレッドシートの IMPORTDATA のように、ブラウザのfetch以外の経路で読む

CORSまわりは条件によって挙動が変わるので、うまく読めなかったときは「fetch以外の経路を試す」くらいの気持ちでいると気が楽です。

実用例:作ったAPIを他ツールから読む

せっかく作ったAPIなので、実際に活用してみましょう。ここでは手軽に試せる2つの使い方を紹介します。

IMPORTDATAで別シートに読み込む小ワザ

スプレッドシートには IMPORTDATA という関数があり、URLの中身を読み込んでシートに展開できます。JSONそのままだと表として展開はされませんが、CSV形式を返すAPIにすると相性が良く、別シートから在庫リストを参照する、といった使い方ができます。

JSONで受け取った内容を確認するだけなら、まずはブラウザでURLを開くのが一番手軽です。データの中身が意図どおりか、目で見て確かめる用途にちょうど良いです。

=IMPORTDATA("https://script.google.com/macros/s/~/exec")

このように別のスプレッドシートのセルに書いておけば、大元のシートを更新するだけで参照側にも反映されます。「データの置き場所は1つ、読む側は複数」という、私が最初にやりたかった形がこれで実現できました。

自作ページや他ツールの入力元にする

在庫リストや店舗一覧のような「一覧データ」は、こうしてAPI化しておくと後々いろいろな場面で使い回せます。自作の確認ページの表示元にしたり、別のツールの入力データにしたりと、応用の幅が広がります。

大事なのは「大元のデータは1か所」で管理し、あちこちはそれを読みに行くだけにすること。これだけで、私のように「どれが最新か分からなくなる問題」からかなり解放されます。

自分でも作れるようになりたい方へ

ここまで読んでくださって、ありがとうございます。最初はコード1文字を打つのも緊張するものですが、今回のように「短い実例を写経して動かす→少しずつ足す」を繰り返すうちに、自然と自分の道具として使えるようになっていきます。私も看護師のかたわら、細切れ時間でここまで来られました。焦らず一歩ずつで大丈夫です。もし本腰を入れて学びたくなったら、体系立てて教われる環境を頼るのも近道ですよ。

Dive into Code(未経験からエンジニアを目指すプログラミングスクール)

関連記事

この記事を書いた人:凛

2児2児のママで現役ナース。夜勤明けの細切れ時間を副業GASに投じ、月5〜8万円の副収入を継続中。「看護師でもコードは書ける」を合言葉に、家事育児とプログラミングを両立する等身大の情報を発信しています。

掲載コードは構文とAPI仕様を確認して載せていますが、お使いの環境に合わせて調整してください。