1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GASで受付原文を変換させずに保存する:Sheets APIのRAW書き込みと読み戻し検証

1
Last updated at Posted at 2026-10-09

GoogleフォームやCRMの問い合わせをスプレッドシートへ転記していると、入力者が書いた文字列が別の値へ解釈されることがあります。

  • =1+1 が数式として評価される
  • 00123 が数値 123 になる
  • 2026/10/09 が日付として扱われる

受付原文を後から確認する用途では、この変換が「入力を保存できた」という成功に見えてしまうのが問題です。

この記事では、Google Apps Script(GAS)から Sheets API の valueInputOption: 'RAW' を使って、書き込み前の文字列・書き込み結果・読み戻した値と数式の有無を検査する実装を作ります。実顧客のフォームには接続せず、架空のテスト用シートで一行を確認するところまでを扱います。

この記事で作るもの

対象は、フォームやCRMの回答をスプレッドシートへ保存するGASを保守している担当者です。次の状態を完成とします。

  1. case_id と raw_text の二列へテスト入力を追加できる。
  2. RAW と文字列型を指定し、値を解釈させない。
  3. APIが返した updatedRange を使って、同じセルを読み戻す。
  4. 値の一致、文字列型、数式なしを確認してから後続処理へ渡す。
  5. 結果不明時に同じ行を盲目的に再追加しない。

受付原文を保存した後に人間確認へ戻す境界

受付返信、CRM登録、AIへの送信はこの実装の完了条件に含めません。

RAWだけでは十分でない

Sheets APIの ValueInputOption では、RAWは入力値を解析せず保存し、USER_ENTEREDは画面入力と同じように解釈すると説明されています。

指定 入力値 保存時の扱い 受付原文での判断
USER_ENTERED =1+1 数式として解釈され得る 原文保管には使わない
RAW 文字列 =1+1 解析せず保存する 今回の方式
RAW 数値 123 数値のまま保存する 00123には戻せない

つまり、RAWを付けるだけでなく、書き込み直前に値が文字列であることを検査します。すでに Number() や日付変換を通った値は、RAWでも元には戻りません。

テスト用シートの準備

本番の回答シートとは分けた新しいスプレッドシートを用意します。

  1. シート名を 受付テスト にする。
  2. A1に case_id、B1に raw_text と入力する。
  3. A:Bに集計式や手動の運用列を置かない。
  4. Apps ScriptをV8ランタイムで開く。
  5. 「サービス」からGoogle Sheets API v4(識別子 Sheets)を追加する。
  6. スクリプトプロパティ TEST_SPREADSHEET_ID にテスト用シートのIDを保存する。

シートIDはURLの /d/ と /edit の間の値です。記事や公開リポジトリへ実環境のID、回答本文、認証情報を貼りません。高度なSheetsサービスと、関連付けたGoogle CloudプロジェクトのSheets APIが有効であることも確認します。

入力を文字列のまま作る

まず外部サービスを呼ばない関数を作ります。trim、Number、日付変換をここへ入れないのがポイントです。

const RAW_TEXT_LIMIT = 1000;

function buildRawRow(caseId, text) {
  if (typeof caseId !== 'string' || !/^TEST-\d{3}$/.test(caseId)) {
    throw new Error('CASE_ID_INVALID');
  }
  if (typeof text !== 'string' || text.length === 0 ||
      text.length > RAW_TEXT_LIMIT) {
    throw new Error('TEXT_INVALID');
  }
  // 原文を保存する段階では、前後の空白や改行も変更しない。
  return [caseId, text];
}

この上限は記事内のサンプル用です。業務の文字数制限やSheetsの最大値を表しません。空白だけの入力を許すかどうかは、原文保存とは別の受付ルールで決めます。

RAWで追加し、返却範囲を読み戻す

次に、APIの返却値から書き込み範囲を取得します。最終行を推測して読み直すと、同時実行や空行の影響を受けます。

function appendRawTest(caseId, text) {
  const expected = buildRawRow(caseId, text);
  const spreadsheetId = PropertiesService.getScriptProperties()
    .getProperty('TEST_SPREADSHEET_ID');
  if (!spreadsheetId) throw new Error('TEST_SHEET_ID_REQUIRED');

  const result = Sheets.Spreadsheets.Values.append(
    { majorDimension: 'ROWS', values: [expected] },
    spreadsheetId,
    "'受付テスト'!A:B",
    { valueInputOption: 'RAW', insertDataOption: 'INSERT_ROWS' }
  );
  const updates = result && result.updates;
  if (!updates || !updates.updatedRange ||
      updates.updatedRows !== 1 || updates.updatedColumns !== 2) {
    throw new Error('WRITE_RESULT_UNCONFIRMED');
  }

  const written = SpreadsheetApp.openById(spreadsheetId)
    .getRange(updates.updatedRange);
  const actual = written.getValues();
  const formulas = written.getFormulas();
  if (formulas.flat().some((formula) => formula !== '')) {
    throw new Error('RAW_FORMULA_DETECTED');
  }
  if (actual.length !== 1 || actual[0].length !== 2 ||
      expected.some((value, index) =>
        typeof actual[0][index] !== 'string' || actual[0][index] !== value)) {
    throw new Error('RAW_READBACK_MISMATCH');
  }
  return { status: 'RAW_WRITE_VERIFIED', range: updates.updatedRange };
}

INSERT_ROWSは新しい行へ追加する指定で、数式化を防いでいるのは RAW です。updatedRange、updatedRows、updatedColumnsの意味は Values.appendの公式リファレンスで確認できます。

getValues()だけでなくgetFormulas()も確認します。値が一致していても、後続処理が別の式へ差し替えられていれば、原文の保管確認として不十分だからです。

書き込み前のローカルテスト

Googleサービスへ接続しないテストを先に実行します。

function testBuildRawRow() {
  const examples = [
    '=1+1', '00123', '2026/10/09', '+10', '-10', '@sample',
    "'quoted", '  空白\n改行  ', 'x'.repeat(RAW_TEXT_LIMIT),
  ];
  examples.forEach((text) => {
    const row = buildRawRow('TEST-001', text);
    if (row.length !== 2 || row[0] !== 'TEST-001' ||
        typeof row[1] !== 'string' || row[1] !== text) {
      throw new Error('TEST_RAW_TEXT_CHANGED');
    }
  });

  const invalid = [null, 123, false, {}, [], '',
    'x'.repeat(RAW_TEXT_LIMIT + 1)];
  invalid.forEach((text) => {
    let actual = '';
    try { buildRawRow('TEST-001', text); }
    catch (error) { actual = error.message; }
    if (actual !== 'TEXT_INVALID') throw new Error('TEST_REJECTION_FAILED');
  });
  try { buildRawRow('bad-id', '=1+1'); }
  catch (error) {
    if (error.message !== 'CASE_ID_INVALID') throw error;
    console.log('RAW_ROW_TESTS_PASS');
    return;
  }
  throw new Error('CASE_ID_REJECTION_FAILED');
}

実行ボタンから appendRawTest や onFormSubmit を直接呼んでも、実際のフォーム送信イベントの検証にはなりません。まず testBuildRawRow で入力境界を確認し、シートへ書き込むテストは専用の関数から一回だけ行います。

テスト用シートへ一行書く

次の関数を一度だけ実行します。

function demoRawWrite() {
  const result = appendRawTest('TEST-001', '=1+1');
  // 本文やシートIDはログへ出さない。
  console.log(result.status);
}

RAW_WRITE_VERIFIED が出たら、A列が TEST-001、B列が文字列 =1+1 のままであることをシートでも確認します。数値 2になっていないことに加えて、コードの getFormulas() が空文字を返すことまでが検証結果です。

入力 期待する保存結果 失敗時の扱い
TEST-001 / =1+1 文字列のまま、数式なし 後続処理を保留
TEST-002 / 00123 先頭のゼロを保持 原本と照合
TEST-003 / 2026/10/09 日付へ変換されない 転記経路を確認
数値 123 書き込み前に TEXT_INVALID 入力処理を修正
IDプロパティなし TEST_SHEET_ID_REQUIRED 設定を確認
読み戻しの値・型が違う RAW_READBACK_MISMATCH AI入力を止める

失敗時に再実行しない理由

このサンプルの append は冪等ではありません。同じ caseId で二回実行すると二行追加されます。

特に注意するのは、書き込み自体は成功した後に、読み戻しやネットワークが失敗する場合です。その時点で「保存されていない」と決めつけて再実行すると、重複受付になります。

  1. 書き込み前の入力エラーなら、入力または設定を修正する。
  2. APIエラーや WRITE_RESULT_UNCONFIRMED なら、テストシートの case_id と実行履歴を照合する。
  3. 数式検出や値の不一致なら、その行をAI入力・返信・CRM登録へ進めない。
  4. 読み取り権限のエラーなら、保存済みの可能性を残して正規の権限を確認する。
  5. 照合が終わるまで、自動リトライや行の削除はしない。

本番で重複を防ぐには、受付IDの一意制約、処理状態、結果不明時の回収手順を別に設計します。この記事の TEST-001 はその代替ではありません。

RAWで解決しないこと

RAWは保存時の解釈を抑えるための指定です。次の問題は別の工程です。

  • すでに数値や日付へ変換された原文の復元
  • CSVを別ソフトで開いた時の再解釈
  • 実顧客の氏名、連絡先、自由記述をAIへ送ってよいかの判断
  • AIが作った返信の正確性、宛先、料金や納期の約束
  • 書き込み成功後に誰が承認し、どこで外部送信を止めるか

保管の成功と、AIへの入力許可・顧客への返信許可は分けて記録します。テスト生成のために実顧客の問い合わせや認証情報を外部AIへ渡す必要もありません。

まとめ:受付処理へ組み込む前のチェックリスト

  • 本番から分離した二列のテスト表を用意した
  • 書き込み直前の値が文字列であることを検査した
  • RAWを指定し、updatedRangeから同じセルを読み戻した
  • 値の一致、文字列型、数式なしを確認した
  • 結果不明時に自動再実行しない回収手順を決めた
  • 保管成功とAI入力・外部送信の承認を別の状態にした

まずは =1+1、00123、日付に見える文字列の三つを、架空のテストIDで確認してください。転記処理へ追加する時は、「どこで原文が数値や日付へ変わったか」を一か所だけ追える状態にしてから、次の工程へ進めます。

Miraigentの無料診断を業務整理に使う場合も、「受付原文が転記先で変わった時、誰が原本と照合するか」を確認項目として残せます。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?