google.script.run の null が undefined になる罠 — 本番だけ出る NaN 表示の自動検出
GASウェブアプリでサーバ側の null がブラウザで undefined になり、=== null の分岐が素通りして NaN が表示される。単体テストでもJSON fixtureでも再現しない理由と、null を落とした fixture で描き直して自動検出する方法。
約3.5万トークンの節約 (API料金換算で約53円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。
この巻物について
「google.script.run の null が undefined になる罠 — 本番だけ出る NaN 表示の自動検出」は、Google WorkspaceカテゴリのAI指示書(MDファイル)です。GASウェブアプリでサーバ側の null がブラウザで undefined になり、=== null の分岐が素通りして NaN が表示される。単体テストでもJSON fixtureでも再現しない理由と、null を落とした fixture で描き直して自動検出する方法。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約3.5万トークン(API料金換算で約53円)・88%のトークンを節約できます。
- カテゴリ
- Google Workspace
- 対応AI
- claude-code、cursor、codex-cli
- ライセンス
- 商用利用可 (再販不可)
- 価格
- 無料
- ゼロから開発時
- 約4万トークン
- この巻物使用時
- 約5,000トークン
- 節約量
- 約3.5万トークン (約53円)
- 更新日
- 2026-09-02
使い方 (AIに渡す3つの方法)
いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。
claude "https://makimono-md.vercel.app/api/v1/files/google-script-run-null-undefined-nan/raw を読み込んで、この指示書どおりに実装して"
中身
google.script.run の null が undefined になる罠と、本番だけ出る NaN 表示の自動検出
Google Apps Script のウェブアプリで、サーバ側の null がブラウザ側で undefined になって届くことによる表示バグと、その再発を機械的に止める方法。単体テストでは原理的に検出できない種類の不具合を対象にする。
症状
画面に NaN が出る。よくある出方:
- 集計見出しが
実績 NaN個 / 予定比 NaN% - 表のセルが
NaN人NaN円NaN件
しかもサーバ側のデータを直接読むと、その項目は正しく null(=未入力)になっている。ログにもエラーが出ない。
原因
google.script.run の戻り値はブラウザへ渡る途中で独自にシリアライズされ、オブジェクトのプロパティが null のとき undefined として届くことがある。そのため次の分岐が素通りする。
// 危険: undefined は null と等しくないので else 側へ落ちる
`${v === null ? "—" : `${n(v)}個`}` // n(undefined) → NaN
Number(undefined) は NaN なので、そのまま画面に出る。
なぜテストで見つからないか
これが厄介なのは、同じデータでも取得経路によって現れ方が違う点にある。
| 経路 | null の届き方 | NaN が出るか |
|---|---|---|
google.script.run(実ブラウザ) | undefined になることがある | 出る |
JSON.stringify を通す経路(コマンドキュー、Web API、ログ出力) | null のまま | 出ない |
サーバ側のロジックを配列で叩く単体テストはもちろん、サーバの戻り値を JSON で保存した fixture を画面に食わせるヘッドレス検証でも再現しない。実ブラウザで人が見るまで誰も気付かない。
対処
1. 数値表示を1か所に集約する
「数値として表示できる値だけを返し、それ以外は null に畳む」ヘルパーを1つ用意し、表示の分岐はこれを通す。
// 数値として表示できる値だけを返し、それ以外は null にする。
// google.script.run はサーバ側の null を undefined にして渡すことがあり、
// null 判定だけだと Number(undefined) が NaN になって画面に出る。
function num(v) {
return v === null || v === undefined || v === "" || !Number.isFinite(Number(v))
? null
: Number(v);
}
使い方:
// 修正前
`${v === null ? "—" : `${n(v)}個`}`
// 修正後
`${num(v) === null ? "—" : `${n(v)}個`}`
すでに ?? "" を通しているエスケープ関数は無害。 例えば esc(v) の実装が String(v ?? "") なら、undefined でも空文字になるので壊れない。危ないのは Number() を経由する表示だけ。既存コードを直すときは === null / !== null を全部拾い、そのうち数値表示に使われているものだけを num() へ寄せる。
2. 「null を undefined に落とした fixture」で描画して自動検出する
jsdom で画面を組み立てる検証を持っているなら、同じ fixture の null を再帰的に消してから描き直し、NaN が1つも出ないことを判定する。これで実ブラウザ固有の差を CI で捕まえられる。
// google.script.run はサーバ側の null を undefined にして渡すことがある。
// JSON 経由では null のまま届くため、この差は実ブラウザでしか現れない。
function dropNulls(value) {
if (Array.isArray(value)) return value.forEach(dropNulls);
if (!value || typeof value !== "object") return;
for (const [key, item] of Object.entries(value)) {
if (item === null) delete value[key];
else dropNulls(item);
}
}
const nullFreeFixture = JSON.parse(await fs.readFile(fixturePath, "utf8"));
dropNulls(nullFreeFixture);
await reloadWith(nullFreeFixture); // fixture を差し替えて画面を描き直す
for (const tabName of ["<検証したい画面1>", "<検証したい画面2>"]) {
document.querySelector(`#tabs button[data-t="${tabName}"]`).click();
// NaN を含む最も内側の要素だけを挙げ、どの表示が壊れたか一目で分かるようにする。
const found = [...document.querySelectorAll("#app *")]
.filter((el) => el.textContent.includes("NaN")
&& ![...el.children].some((child) => child.textContent.includes("NaN")))
.map((el) => `<${el.tagName.toLowerCase()}> ${el.textContent.replace(/\s+/g, " ").trim().slice(0, 60)}`);
if (found.length) throw new Error(`${tabName} に NaN が${found.length}箇所: ${[...new Set(found)].slice(0, 3).join(" / ")}`);
}
失敗メッセージは「最も内側の要素」に絞ること。 画面全体のテキストを出すと数千文字のブロックになり、どこが壊れたのか読み取れない。上の形なら <td> NaN人 と出る。
fixture の差し替えと再描画
fixture をモジュールスコープの let にしておき、モックの応答関数がそれを返すようにしておくと差し替えられる。再描画の完了は、再描画前の要素が DOM から外れたことで判定するのが確実(内容が同じだと文字列比較では判定できない)。
async function reloadWith(next) {
const stale = document.querySelector("#tabs button");
fixture = next;
window.reload(); // アプリ側の再取得+再描画
await waitFor(() => !document.contains(stale));
}
判定が終わったら元の fixture に戻してから画面ダンプを書き出す。差し替えたままだと、あとでダンプを読んだ人が実データと取り違える。
導入時に必ず起きること
この判定を入れると、その場で未発見の同型バグが出てくるのが普通。実例では、月見出しの NaN個 / NaN% を直した直後にこの判定を追加したところ、別の列(未入力の人数欄)が NaN人 になっていたことが即座に分かった。人が見ていた画面にずっと出ていたのに、誰も報告していなかった。
最初の実行で FAIL が出ても検証コードを疑わず、まず画面を疑うこと。
チェックリスト
-
num()に相当する正規化ヘルパーを1つ用意した -
=== null/!== nullを全部拾い、数値表示のものをnum()に寄せた -
nullを落とした fixture で全画面を描き直す判定を追加した - 失敗メッセージが「最も内側の要素」に絞られている
- 判定後に元 fixture へ戻してからダンプを書き出している
- 初回実行で出た FAIL を、検証コードではなく画面の側で直した
よくある質問
+「google.script.run の null が undefined になる罠 — 本番だけ出る NaN 表示の自動検出」とは何ですか?
GASウェブアプリでサーバ側の null がブラウザで undefined になり、=== null の分岐が素通りして NaN が表示される。単体テストでもJSON fixtureでも再現しない理由と、null を落とした fixture で描き直して自動検出する方法。
+どれくらいトークン(費用)を節約できますか?
ゼロから開発すると約4万トークンかかりますが、この巻物を使えば約5,000トークンで済みます。差し引き約3.5万トークン(API料金換算で約53円)・88%の節約です。
+どうやって使いますか?
無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。
+どのAIツールに対応していますか?
claude-code、cursor、codex-cli に対応しています。
+商用利用できますか?
ライセンスは「商用利用可 (再販不可)」です。
🤝 自分でAIを動かすのは、まだ不安…という方へ
この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。
関連する巻物
GAS完全自動化テンプレ — Driveコマンドキュー方式
Google Apps Script の「毎回エディタで▶実行」を根絶。Drive 経由のコマンドキューで、初回1クリック以降は AI がすべての GAS 関数をリモート実行できるようになるテンプレート指示書。
人間の手入力台帳を壊さずに自動更新する — GAS Web App upsert 設計
各PC/各拠点の点検結果を、人間が手運用しているスプレッドシート台帳へ自動反映する。手入力列とコメントを絶対に壊さない突合設計、タブ/列の解決、並行POST対策、配布シークレットの落とし穴まで。
数式まみれの業務スプレッドシートを、Webアプリから壊さずに編集させる型
ArrayFormula と per-row 数式が混在する台帳を、セル単位 allowlist・dry-run 既定・適用前バックアップ・触っていないセルの数式不変検査で安全に書き換える設計手順。列ごとの数式復元と、テストが緑のまま壊れる典型例つき。
この巻物、誰かのトークンも救えます
𝕏 で節約レシートをシェア