JSONのフォーマット・構文検証・JSON Schemaの違いと実務での使い分け
要約 (TL;DR)
先月、あるチームが feature_flags.json の1行を直してデプロイした。JSON.parse は通り、CIも緑、stagingに上げると flags.checkout_v2 が "true" という文字列だったため、すべての決済分岐が旧バージョンに落ちた。「JSONを確認した」という言葉は実際には整形・構文検証・構造検証という3つの作業の合成語なのに、その日のチームは前2つしか実行していなかったわけだ。**整形(pretty-print)**は空白と改行を入れ直して見た目を変えるだけで、何も検証しない。**構文検証(syntax validation)**はRFC 8259を基準に括弧・引用符・エスケープが正しいかを見る段階で、JSON.parse がこの仕事をする。**構造検証(structural validation)**はその後、「この形は僕のコードが期待する形か」を問う段階で、JSON Schemaが作られた理由はまさにこれだ。3つを「JSON検証ボタン1つ」として扱った瞬間、上のような無音障害が起きる。読むときは整形、入力を受けるときはパース、APIの境界・設定ファイル・メッセージ境界のような信頼境界ではAjvのようなバリデータでスキーマまで——3段階をそれぞれの場所に置くパイプラインが安全だ。
背景・コンセプト
JSONはRFC 8259(同等のECMA-404)で定義される。文法は小さく単純だ。文書は文字列・数値・true・false・null・配列・オブジェクトのいずれかで、文字列は二重引用符で囲み、\n、\t、\"、\\、\uXXXX(Unicodeコードユニット)などのエスケープを持つ。数値は整数と実数を区別せず10進文法に従う。オブジェクトは文字列キーを持つメンバーの順序のない集合で、配列は順序付き値のリスト。文字列の外側の空白には意味がない。
JSONが意図的に含まないものも重要だ。コメントなし、末尾コンマなし、シングルクォート文字列・16進数・2進数リテラルもない。非ASCII文字はUTF-8バイトをそのまま書くか \u エスケープでのみ表現する。JSON5やHJSONは一部の規則を緩めた別フォーマットであり、厳格なJSONパーサはこれらを受け付けない。
パースに成功したというのは「文法的に正しい」だけを意味し、「意味的に正しい」とは別だ。{"user": "x", "pass": "y"} は完全に有効なJSONだが、エンドポイントが {"username": "...", "password": "..."} を期待していたなら、アプリケーションから見れば誤ったデータだ。これを捕まえるにはスキーマ——機械が読める「許容される文書の形の仕様」——が必要で、その標準がJSON Schemaだ(現行推奨メタスキーマはDraft 2020-12)。必須フィールド、型、enum・const、正規表現による文字列パターン、数値範囲、配列の items・一意性、オブジェクトの properties・additionalProperties、allOf・oneOf・anyOf・$ref による合成までサポートする。Ajv 8.12 のようなバリデータはスキーマを一度コンパイルして関数にしておくため、ホットパスでも検査コストはほぼ無視できる——僕が運用していたあるAPIでは、コンパイル済みAjvの検証1回で平均数十マイクロ秒の領域だった。
整形はこの中で最も単純だ。意味を変えず、空白とインデントだけを調整する。JSON.stringify(obj, null, 2)、jq .、IDEのフォーマッタが既にやっている仕事で、人間に読ませるためのものであり、機械にとっては意味がない。
比較・データ
| 観点 | 整形 | 構文検証 | JSON Schema検証 |
|---|---|---|---|
| 目的 | 読みやすく空白を整える | 文字列がJSONとしてパースできるか確認 | パース済みの値が期待した形か確認 |
| 検出 | なし(空白のみ調整) | 括弧不一致、引用符エラー、不正なエスケープ、末尾コンマ | 必須フィールド欠落、型誤り、範囲超過、未知のキー |
| 検出できないもの | 構造・意味の問題 | 期待した形・ビジネスルール | スキーマにないルール、フィールド間の関係 |
| 一般的なツール | JSON.stringify(obj, null, 2)、jq、IDEフォーマッタ | JSON.parse、jq -e、各言語のパーサ | Ajv 8.x、python-jsonschema、OpenAPIバリデータ |
| 適用箇所 | 開発ツール・ログ | 全てのパース境界(暗黙) | APIリクエスト/レスポンス、設定ロード、メッセージ境界 |
3つの列は「どれか1つ」ではなく、順次階層だ。12MBの package-lock.json を1行のまま眺めていてインデント付きの形で見やすくするのが整形で、それが「検証」と呼ばれた瞬間に事故が始まる。読むために整形し、文字列が壊れていないかパースで弾き、形が合っているかスキーマで確認する。前2段階だけ適用して最後を抜かすのが、よくある穴だ。
実践シナリオ
シナリオ1 — APIレスポンスのデバッグ。 外部決済ゲートウェイが600行のJSONを1行で返してくる。ブラウザ開発ツールのNetworkパネル、ローカルフォーマッタ、curl ... | jq . のようなパイプ1本で人が見られる形になる。ここでは検証は起きておらず、目的は「どこがおかしいかを目で確認」するだけ。この段階で「検証した」と呼ばないのが大事だ。
シナリオ2 — 設定ファイルのロード。 サービスが起動時に config.json を読むとする。厳格なJSONパーサは末尾コンマのような構文エラーを掴んで起動を止めてくれる。これは正しい挙動だ。だが冒頭で挙げた事例のように、retries: 3 であるべき場所に retries: "three" が入っていてもパースは通り、問題はコードが文字列を数値と比較しようとした時点でようやく姿を現す。あるチームに勧めたパターンはこうだ——起動の最初の1行でAjv compile(schema) で作った検証関数を呼び、必須フィールド・型・範囲・enum を確認し、違反があれば process.exit(1)。5分の追加が、その後2件のstaging事故を止めた。
シナリオ3 — OpenAPI契約検証。 チームがOpenAPI 3.1文書にエンドポイント・リクエストボディ・レスポンス形を components.schemas で記述していたとする。契約テストは仕様の例ペイロードを同じスキーマで検証して、サーバー実装が「文字列であるべき場所に整数」を返す drift をクライアントが壊れる前に捕まえる。エンジンは孤独な設定ファイル検証と同じJSON Schemaバリデータで、違いは適用規模だけだ。
よくある誤解
「JSON.parse だけで検証は終わり」。 文法だけを見ているだけで、形は見ていない。パーサは半分が空のオブジェクトでも喜んで返す。JSON.parse は「壊れた文字列を遮断する」役割と捉え、信頼境界ではスキーマ検査をもう一層重ねるべきだ。
「JSON Schemaはサーバー専用」。 ブラウザでリクエスト前に検証すればユーザーフィードバックが即座に返り、サーバー負荷も下がる。Ajvをはじめ多くのバリデータがブラウザでよく動く。サーバー検証は依然必須だが——クライアントは信用できない——クライアント側検証はUXを改善し、セキュリティモデルを弱めない。
「JSON5はコメントが書けるJSON」。 JSON5はコメント・引用符なしキー・末尾コンマ・16進数リテラルなどを足す。人が編集する設定フォーマットとしては親切だが(最も有名な使用例である tsconfig.json は実はJSONCというまた別の非標準上位集合だ)、RFC 8259を厳格に守るコンシューマはこれを受けない。コンシューマが明示的にサポートする場合のみJSON5/JSONCを使い、ネットワークに出るか任意のパーサを通すなら必ず厳格JSONを書き出すこと。
「YAMLはインデントを使うJSON」。 全ての有効JSON文書は有効なYAMLだが、YAMLはアンカー・エイリアス、タグ型、1ファイルの複数ドキュメント、ブロック・フォールドスカラー、インデント敏感パースなどJSONにない機能を載せる。有名な落とし穴は「ノルウェー問題」——YAML 1.1は引用符なしの NO をブール false と解釈する。「読みやすいから」JSONをYAMLに切り替える判断は、新しい問題を招く。
チェックリスト
- ただ読みたいだけか? 整形だけで済ませる。「検証した」と呼ばない。
- データが信頼境界を越えるか(HTTPリクエスト、メッセージキュー、設定ファイル)? パースだけでなくJSON Schemaも。
- 未知のフィールドを許すか決めたか? スキーマに
additionalProperties: falseを設定し、拒否・除去のポリシーを定めておく。 - エラーメッセージが行動可能か? Ajvの
allErrors: trueのように、すべての違反を一度に見せる設定にする。 - スキーマが実装コードの近くに住んでいるか? 仕様と実装の drift は、スキーマが両者の単一真実源になっているときの方が広がりにくい。
- フォーマットがJSONに合うレベルか? 人が編集する設定でパーサを自分で選べるなら、JSON5/JSONC・TOMLの方が寛容。機械間交換なら厳格JSONを維持。
関連ツール
Patrache Studioの JSONフォーマッタ はブラウザ内で動くため、貼り付けたペイロードが端末外に出ない——個人情報を含む本番レスポンスを覗くときに便利だ。JSONペイロードは1人で歩かない。中にエンコードされたバイナリが入っているなら Base64・URLエンコーディング:なぜ必要で、いつ誤用されるか でサイズが膨らむ理由と、別の転送手段が適切な場面を扱う。IDが含まれているなら UUID v1・v4・v7の比較とDB主キー設計 で、サーバーがどのバージョンを発行するかが下流のインデックス・ソート・キャッシュに影響する理由を見られる。
参考文献
- IETF RFC 8259, “The JavaScript Object Notation (JSON) Data Interchange Format” — https://datatracker.ietf.org/doc/html/rfc8259
- JSON Schema仕様(Draft 2020-12) — https://json-schema.org/specification
- Ajv — Another JSON Schema Validator — https://ajv.js.org/
- MDN, “Working with JSON” — https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON