0
0

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】WebアプリでサーバーからHTMLにJSONを渡すときの「鉄板」実装パターン(文字化け・構文エラー完全回避)

Last updated at Posted at 2025-11-19

Google Apps Script (GAS) でWebアプリを作成する際、サーバー側(.gs)のデータをオブジェクトや配列のままクライアント側(.html)に渡したい場面がよくあります。

しかし、安直に JSON.stringify して渡そうとすると、データの中に 日本語、改行コード、ダブルクォーテーション(") などが含まれている場合に、ブラウザ側で SyntaxError や文字化けが発生してハマることがあります。

この記事では、あらゆる文字が含まれていても絶対にエラーにならない、堅牢なデータの受け渡し方法をメモとして残します。

❌ よくある失敗例(アンチパターン)

サーバー側で JSON.stringify した文字列を、そのまま Scriptlet (<?= ... ?>) で HTML に埋め込む方法です。

GAS側 (.gs)
JavaScript
function doGet() {
  const data = {
    message: 'これは "引用符" です', // ← これが鬼門
    note: '改行も\nあります'
  };
  const template = HtmlService.createTemplateFromFile('index');
  template.jsonData = JSON.stringify(data); 
  return template.evaluate();
}
HTML側 (.html)
HTML
<script>
  // エラーになる!
  const data = JSON.parse('<?!= jsonData ?>');
</script>
発生するエラー

ブラウザのコンソールに以下のようなエラーが出ます。

Uncaught SyntaxError: Bad escaped character in JSON
または
Uncaught SyntaxError: Unexpected token

❌ なぜ普通に埋め込むと失敗するのか?

「日本語や特殊記号が含まれているからエラーになる」と思われがちですが、実は { msg: "Hello" } のような最もシンプルなデータであっても、以下の方法ではエラーになります。

JSON.stringify はデータを {"key":"value"} のような文字列に変換しますが、この中には必ず ダブルクォーテーション(") が含まれます。 これをHTMLやJSのコード内に埋め込むと、HTMLの属性を閉じる " や、JSの文字列を閉じる ' と衝突してしまい、構文が破壊されるためです。

失敗するメカニズム

  1. データのJSON化
    JSON.stringify は、仕様上必ず キーと値をダブルクォーテーション(") で囲みます。
    JavaScript
    // 元データ: { msg: "Hello" }
    // 変換後:   {"msg":"Hello"}
    
  2. HTMLへの埋め込み(衝突)
    これを HTML の属性(valueなど)に埋め込むと、HTMLの属性を囲む " と JSONデータ内の " が衝突します。
    HTML
    <input type="hidden" value="<?!= jsonData ?>">
    
    <input type="hidden" value="{"msg":"Hello"}">
    
  3. ブラウザの解釈
    ブラウザは value="{" までを属性値とみなし、そこで属性を閉じてしまいます。 その後に続く msg":"Hello"}" が「タグ内の不正な文字(ゴミ)」として扱われ、SyntaxErrorMalformed HTML(形式が正しくないHTML) エラーが発生します。

結論

つまり、データの中身に関わらず、「JSON文字列を生のままHTML属性(valueなど)に埋め込むこと」自体が、構造的に不可能です。

これに加えて、GASのテンプレートエンジン特有の挙動により、改行コードやエスケープ文字(\)が含まれると Bad escaped character エラーも併発するため、問題はより深刻化します。

だからこそ、「引用符も記号も含まない形式(Base64)」 に変換して受け渡す方法が、唯一の安全な解決策となります。

✅ 解決策:Base64エンコード(UTF-8対応)× 隠しフィールド

この問題を解決する鉄板の方法は以下の通りです。

  1. サーバー側: データを Base64 にエンコードして渡す。(" や改行を英数字に変換して無害化する)

  2. 受け渡し: <script>タグ内ではなく、<input type="hidden"> に埋め込む。(JSの構文エラーリスクをゼロにする)

  3. クライアント側: 受け取ったBase64を、日本語(UTF-8)に対応した方法でデコードする。

1. サーバー側の実装 (.gs)

GAS標準の Utilities を使ってエンコードします。
ポイントは、そのままエンコードするのではなく、一度 UTF-8 のバイト配列 (getBytes()) に変換してから WebSafe な Base64 にすることです。これをしないと日本語が文字化けします。

JavaScript
// ✅ 解決策のサーバー側コード (.gs)
function doGet() {
  // 引用符、改行、絵文字を含んだ「壊れやすい」データ
  const data = {
    id: 101,
    name: "山田 太郎",
    memo: "ダブルクォート(\")も、\n改行も、絵文字(🎉)も、\n全て文字化けせずに渡せます。"
  };

  const template = HtmlService.createTemplateFromFile('index');
  
  // 【重要】JSON化 -> UTF-8バイト配列 -> Base64(WebSafe)エンコード
  const jsonString = JSON.stringify(data);
  const blob = Utilities.newBlob(jsonString);
  template.encodedData = Utilities.base64EncodeWebSafe(blob.getBytes());

  return template.evaluate()
    .addMetaTag('viewport', 'width=device-width, initial-scale=1')
    .setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
2. クライアント側の実装 (.html)

HTML側では、隠しフィールドで受け取り、JSでデコードします。 window.atob() は日本語(マルチバイト文字)を直接扱うと文字化けするため、Uint8ArrayTextDecoder を組み合わせて復号します。

HTML
<!DOCTYPE html>
<html>
  <body>
    <input type="hidden" id="server-data" value="<?!= encodedData ?>">

    <h1>データ受け渡しテスト</h1>
    <div id="output"></div>

    <script>
      document.addEventListener('DOMContentLoaded', () => {
        // 1. 隠しフィールドからBase64文字列を取得
        const base64String = document.getElementById('server-data').value;
        
        // 2. デコードしてオブジェクトに戻す
        const data = decodeBase64Json(base64String);
        
        console.log(data);
        document.getElementById('output').innerText = data.memo;
      });

      /**
       * 日本語対応 Base64デコード関数
       * @param {string} base64String
       * @return {Object|null}
       */
      function decodeBase64Json(base64String) {
        if (!base64String) return null;
        try {
          // WebSafe Base64 ('-' -> '+', '_' -> '/') に置換
          const base64 = base64String.replace(/-/g, '+').replace(/_/g, '/');
          
          // ブラウザ標準機能でデコード (この時点ではLatin1)
          const binaryString = window.atob(base64);
          
          // バイナリ文字列を Uint8Array (バイト配列) に変換
          const len = binaryString.length;
          const bytes = new Uint8Array(len);
          for (let i = 0; i < len; i++) {
            bytes[i] = binaryString.charCodeAt(i);
          }
          
          // TextDecoder で UTF-8 として文字列化 (これで文字化けしない!)
          const decoder = new TextDecoder('utf-8');
          const jsonString = decoder.decode(bytes);
          
          return JSON.parse(jsonString);
        } catch (e) {
          console.error("Base64 Decode Error", e);
          return null;
        }
      }
    </script>
  </body>
</html>

まとめ

GASからHTMLへ複雑なデータを渡すときは、以下の手順を踏むのが最も確実です。

  1. GAS側: JSON.stringifyUtilities.newBlob().getBytes()Utilities.base64EncodeWebSafe

  2. HTML側: <input type="hidden"> で受け取る

  3. JS側: atobUint8ArrayTextDecoder('utf-8')JSON.parse

この構成にしてからは、Bad escaped characterUnexpected token といったエラーには一切悩まされなくなりました。 日本語や特殊記号を含むデータを扱う際は、ぜひこのパターンを使ってみてください。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?