やりたいこと
Railsでビューを作っていると、コントローラーで用意したデータをJavaScript側でも使いたい場面がわりとある。
<script type="application/json"> タグにJSONとして埋め込む方法を紹介する。DjangoやNext.jsでも使われているパターンだが、素のRailsには専用ヘルパーがないので自前で実装する必要がある。
コード
まずRails側でデータを用意する。
class HomeController < ApplicationController def index @products = [ { id: 1, name: "りんご", price: 120, category: "果物", in_stock: true }, { id: 2, name: "バナナ", price: 80, category: "果物", in_stock: true }, { id: 3, name: "にんじん", price: 60, category: "野菜", in_stock: false }, { id: 4, name: "トマト", price: 150, category: "野菜", in_stock: true }, { id: 5, name: "ぶどう", price: 300, category: "果物", in_stock: false } ] end end
で、erb側は <script type="application/json"> でデータを渡す。
<script type="application/json" id="products-data"> <%= @products.to_json.html_safe %> </script>
これをJS側でデータロードする。 やってることは究極的には JSON.parse(el.textContent)
type Product = { id: number; name: string; price: number; category: string; in_stock: boolean; }; /** * script[type="application/json"] に埋め込まれたJSONをパースして返す。 * XSSを避けるため innerHTML ではなく textContent で取得する。 */ function readJsonScript<T>(elementId: string): T | null { const el = document.getElementById(elementId); if (!el || el.getAttribute('type') !== 'application/json') return null; return JSON.parse(el.textContent ?? 'null') as T; } const products = readJsonScript<Product[]>('products-data'); console.log(products);
同じ目的を達成できる data-* 属性と比較する。
data-* を使うと次のように書ける。
<div id="app" data-products="<%= @products.to_json %>"></div>
const el = document.getElementById('app')
const products = JSON.parse(el.dataset.products)
2つのアプローチを比較するとこうなる。
| script[type=json] | data-* | |
|---|---|---|
| データ量 | 大量OKサイズ制限なし | 大量データは重くなる(HTML属性のエスケープ処理が走る) |
| 型 | 配列・ネストOK | 文字列として埋め込むので毎回JSON.parseが必要 |
| 紐付け | IDで参照(要素と別) | 要素に直接紐付く |
| 可読性 | HTMLとデータが分離してすっきり | 属性が長くなり見づらい |
| エスケープ | .to_json.html_safeでOK |
html_escapeが二重にかかる場合がある |
data-* が向いているのは要素自体に関するシンプルな値(1〜2個のプリミティブ)や、StimulusなどのJSフレームワークと data-controller / data-value を組み合わせる場面だ。
<button data-user-id="42" data-confirm-message="削除しますか?">削除</button>