今までの記事一覧
- GASでGoogleForm回答を取得するなら lastRow?(e)?試してみた
- onFormSubmit(e)を手動実行でデバッグする方法
- どっちを使う?onFormSubmit(e)の values と namedValues の違いと使い分け
- onFormSubmit(e) の e.values 配列順のしくみ
- Googleフォームで質問を変えても壊れない!cleanFormData(e)でnamedValues防御力をアップ
- Googleフォームの質問変更に負けない!「部分一致」と「秘密の暗号」でcleanFormData(e)の防御力を鉄壁に <この記事
- 手動コピペはもう卒業!Googleフォームの回答別に処理を自動仕分け
- Googleフォームで同時に大量送信されても踏ん張る!LockServiceで順番制御!try - catch - finally でバトンを繋げ!
- LockServiceでは順番は守れない?受付番号で順序を保証する方法
前提
この記事は、フォーム回答を保存している スプレッドシート側のGAS を前提にしています。
トリガーは以下を設定しています。
- スプレッドシートから
- フォーム送信時
おさらい:cleanFormData(e)完全一致で堅牢化
前回は ユーティリティ関数cleanFormData(e)を使うことで、質問文が変わってもエラーでプログラムが止まらない仕組みを作りました。
でも、エラーにならなくても データが空っぽ だったら困りますよね。
今回はさらに一歩進んで、多少の変更があっても、中身のデータをちゃんと拾い上げる、本当の意味で最強のコードに進化させます!
「だいたい合ってればOK」にする!部分一致マッチング戦略
前回作ったcleanFormData(e) はこれ。
function cleanFormData(e) {
const namedValues = e.namedValues;
// ここで「フォームの質問文」と「プログラムで使いたい名前」を紐付ける
return {
// 左側:プログラムで使う名前 / 右側:実際のフォームの質問文
email: namedValues["メールアドレス"] ? namedValues["メールアドレス"][0] : "",
name: namedValues["お名前"] ? namedValues["お名前"][0] : "",
body: namedValues["お問い合わせ内容"] ? namedValues["お問い合わせ内容"][0] : ""
};
}
ここでフォームの質問分が「お名前を入力してください」に変更されたら?
エラーにはなりませんが空欄になってしまい、欲しいデータが取得できません。
質問文が「お名前」でも「お名前をどうぞ」でも「名前を入れてください」でもヒットさせたいですよね。
じゃあいっそ 「名前」が入ってたらOKってことにしちゃいましょう。
for文とincludesでcleanFormData(e)を部分一致型に
ユーティリティ関数cleanFormData(e)をこうしてみます。
function cleanFormData(e) {
const namedValues = e.namedValues;
const data = {};
// 1. 全ての質問項目をループ
for (let key in namedValues) { //keyには「質問文」が入っている
const value = namedValues[key][0];
// 2. 質問文の中に「メール」が入ってたら email に入れる
if (key.includes("メール")) {
data.email = value;
}
// 3. 質問文の中に「名前」が入ってたら name に入れる
else if (key.includes("名前")) {
data.name = value;
}
// 4. すべての質問を「質問文」で保存
// これを書いておけば、項目が増えても メイン関数側で
// formData["住所を入力してください"] みたいにすれば呼び出せる
data[key] = value;
}
return data;
}
質問文に
- 「メール」が入ってたら
emailという名前で保存 - 「名前」が入ってたら
nameという名前で保存
これは長い質問文に「email」「name」というニックネームを付けるイメージです。
ニックネームを付けない項目は「質問文そのまま」で呼び出します。
では実験してみましょう。
フォームはこんな感じで

前回と同じようにメイン関数でログを取ってみます。実行!
function onFormSubmit(e) {
const formData = cleanFormData(e);
Logger.log("email:"+ formData.email); //ニックネームが付いているものはニックネームで
Logger.log("name:"+ formData.name);
Logger.log("body:"+ formData["お問い合わせ内容をどうぞ"]); //ニックネームがないものは質問文で
}
見事!データ取得に成功しました。

「メールアドレス」「お名前を入力してください」はそれぞれemail nameで呼び出すことに成功。
ニックネームが付いていない「お問い合わせ内容をどうぞ」はformData["お問い合わせ内容をどうぞ"]で取得することができました。
質問が増えてもらくちん
前回のこの書き方は、質問が増えたとき、取得したい質問の数だけ三項演算子を追加する必要がありました。
つまり、項目が増えるたびに記述が増え、保守性が下がるという問題です。
return {
email: namedValues["メール"] ? namedValues["メール"][0] : "",
name: namedValues["名前"] ? namedValues["名前"][0] : "",
age: namedValues["年齢"] ? namedValues["年齢"][0] : "", // 追加
tel: namedValues["電話"] ? namedValues["電話"][0] : "", // 追加
body: namedValues["内容"] ? namedValues["内容"][0] : "" // 追加
//この下にどんどん追加
};
今回ご紹介した新しいcleanFormData(e)では、for文ですべての質問を自動的に[0]を外した形で保存し、 formData["質問文"]で取得することができます。
その中で、ニックネームで呼びたいと思うものは、これをif と else if の列の中にコピペして、★部分を書き換えればOK。
else if (key.includes("★")) {
data.★ = value;
}
もっと簡単に書くなら1行でこれでもOK。
else if (key.includes("★")) { data.★ = value; }
ここまでのまとめ
つまり、ここが進化
-
クリーン化([0]を外す作業)の共通化:
前回はすべての行で [0] や ? : "" を書いていましたが、今回はループの頭で1回やるだけ。これでコードが劇的にスッキリします。 -
「とりあえず保存」の安心感:
前回はreturnに書き忘れたデータは消えてしまいましたが、今回はdata[key] = value;の中に全部の質問文と回答が入っているので、if 文を書き忘れてもformData["質問文"]で呼び出せます。
むしろ、ニックネームで呼びたいものだけ、ifとelse ifの列の中にコピペして入れればOK。
でもこれって「名前を入れて」「名前(ふりがな)」ってあったら両方ともヒットするんちゃう?
そうなのよ!なのでここでもう1つ、部分一致を使ったテクをご紹介します。
質問に暗号を仕込め
まず、「名前を入れて」「名前(ふりがな)」だけならif文の順番を入れ替えて
else if (key.includes("ふりがな")) の後に
else if (key.includes("名前")) を入れれば解決するけどね。
今回は別のアプローチとして、今覚えたばかりの部分一致を使う方法をやってみましょう。
フォームの質問文に暗号を仕込みます。
今回は暗号 §name §kanaを使いましょう。
「お名前を入力してください。§name」「お名前(ふりがな)を入れてください §kana」のように、質問文の末尾に暗号を入れます。
暗号は他の質問と被らないように注意してね。§name/§nametakeみたいに一部だけの一致もNGです。
暗号は、「§name」でもいいし「〈q1〉」でもいいし、自分がわかりやすいもので他の質問と違っていればなんでもOK。
そしてこれを部分一致のキーとして呼び出します。
function cleanFormData(e) {
const namedValues = e.namedValues;
const data = {};
// 1. 全ての質問項目をループ
for (let key in namedValues) {
const value = namedValues[key][0];
// 2. もし質問の中に「§email」が入ってたら、一律で email に入れる(メール部分)
if (key.includes("§email")) {
data.email = value;
}
// 3. もし「§name」が入ってたら name に入れる(名前部分)
else if (key.includes("§name")) {
data.name = value;
}
// 4. もし「§kana」が入ってたら kana に入れる(ふりがな部分)
else if (key.includes("§kana")) {
data.kana = value;
}
// 5. すべての質問を「質問文」で保存
data[key] = value;
}
return data;
}
この「暗号(ID)方式」の真のすごさは、
質問文をどれだけいじっても、コードを1文字も直さなくていい ことにあります。
- 「お名前 §name」が
- 「【必須】お名前を入力してください §name」に変わっても
- 「Name(English Please) §name」にされても
これ全部、プログラム側は includes("§name") だけでキャッチできちゃうんです。
暗号 §name が付いてるから。
そして
- 「家族のお名前 §family」
- 「会社の名前 §company」
なんかは「名前」と付いていても §name がないから拾わない。
最強すぎない?
フォームに表示されちゃうけどね
ただ、この案のデメリットは、こんなふうに暗号がフォームに表示されてしまうこと。
回答者に「§kanaってなんぞ?」と思われない工夫が必要です。
例えば....
- さりげない系:
(ID:01),[Q1] - デザイン系:
|email,‣ name - 「そういう仕様です」系:
(管理番号: A-1)
あなたのセンスが問われます(笑)
あ、あたりまえだけど、フォームの質問順が変わったからと言って[Q1]の数字を変えちゃダメですよ。
フォームの表示が[Q3][Q1][Q5]みたいな並びになってしまったとしてもそこはぐっと我慢して、暗号は絶対変えないでね。
まとめ
というわけで、今回はフォームの質問を部分一致で取得する方法でした。
それぞれメリット、デメリットがありましたね。
実務のシチュエーションに合わせて、最適な防御策を選べるよう表にしてみました。
| 手法 | メリット(できること) | デメリット(注意点) | おすすめのシーン |
|---|---|---|---|
| 完全一致 | コードが直感的で、初心者でも読みやすい。 | 質問文が1文字でも変わるとデータが取れなくなる。 | 質問文が絶対に変わらない、自分だけが管理するフォーム |
| 部分一致 | 「名前」「メール」等の言葉があれば、多少の文言変更をスルーできる。 | 「名前」と「名前(ふりがな)」等、似た言葉が複数あると混ざるリスクがある。 | 運用者が語尾などを微調整する、一般的なフォーム |
| 暗号方式 | 質問文をどれだけ変えても、IDさえあれば100%確実に取得できる。 | 設問に「§name」等が見えてしまい、少し不格好。運用ルールを徹底する必要がある。 | 質問文が頻繁に変わる、または項目数が非常に多いプロ仕様のフォーム |
迷ったときは?
最初は「部分一致」で作り始めてみて、もし「項目が被ってうまく判定できない!」となった特定の項目だけ「暗号方式(ID)」に切り替える……というハイブリッドな運用もいいかもしれませんね!
以上です。
「もっと良い方法があるよ!」って方はコメントで教えていただけると嬉しいです。
この記事が、同じような悩みを持った方の助けになりますように。

