JSON Diffガイド:2つのJSONドキュメントの差分を見つける
2つのJSONドキュメントを比較するのは簡単に思えますが、キーの順序変更、ネストされた配列の変更、空白のノイズが絡むと話は別です。単純なテキストdiffは、データが同一であってもキーの移動をすべて削除と挿入として報告します。このガイドでは、JSON比較が必要な場面、さまざまなアプローチとそのトレードオフ、そして開発ワークフローでJSON diffを効果的に活用する実践的なヒントを解説します。
JSON比較が必要な場面
JSON diffは単なる便利機能ではありません。プロの開発現場で常に発生する実際の問題を解決します。
APIのバージョン管理と変更検出
サードパーティAPIが新バージョンをリリースした際、何が変わったかを正確に把握する必要があります。v1のレスポンスサンプルとv2のレスポンスサンプルを比較することで、追加・削除・改名・型変更されたフィールドの正確なレポートが得られます。これは不完全・不正確な場合もあるchangelogドキュメントを読むよりはるかに確実です。
設定のトラッキング
package.json、tsconfig.json、appsettings.json、カスタムアプリケーション設定などの設定ファイルは時間とともに変更が積み重なります。2つの異なるデプロイメントやGitリビジョンの設定を比較することで、動作していた環境と壊れた環境の間で何が変わったかを理解できます。
テストの検証
スナップショットテストや統合テストでは、期待される出力がJSONドキュメントであることがよくあります。テストが失敗した際、JSON diffはどのフィールドがどう変わったかをすぐに示してくれます。アサーションが失敗したという事実だけでなく、具体的な差分がわかります。
データベースレコードの監査
アプリケーションがレコードを更新する際、変更前後のJSON diffを監査ログに保存することで、何が・誰によって・いつ変更されたかのコンパクトで人間が読める履歴が残ります。
テキストベース比較と構造的比較
JSONを比較するアプローチは根本的に2つあり、間違った方を選ぶと膨大なノイズが生まれます。
テキストベースdiff
テキストdiff(git diffやUnixのdiffコマンドなど)はJSONをプレーンテキストとして行ごとに比較します。高速でJSONパースが不要ですが、フォーマットの変更もすべて差分として扱います。
同じデータでも異なるインデントでフォーマットされていたり、オブジェクトのキーが異なる順序にあったりすると、テキストdiffはJSONの値が同一であっても大量の差分を報告します。
// "Before"(ある順序のキー){"name": "Alice","id": 42}// "After"(キーが並び替えられた){"id": 42,"name": "Alice"}
テキストdiffは両方のキーを変更として報告します。構造的diffは差分なしと報告します。
構造的diff
構造的diffは、まず両方のJSONドキュメントをインメモリ表現に解析し、その後解析済みの構造を比較します。これにより以下が実現されます。
- オブジェクトのキー順序は関係なし —
{"a":1,"b":2}と{"b":2,"a":1}は同一 - 空白とインデントは完全に無視
- 実際の値の差分のみが報告される
JSONデータを比較する場合、構造的diffがほぼ常に正しい選択です。
| 観点 | テキストdiff | 構造的diff |
|---|---|---|
| キーの並び替え | 変更として報告 | 無視 |
| 空白の変更 | 変更として報告 | 無視 |
型の変更( | 報告 | 報告 |
| ネストされた値の変更 | 報告(ノイズあり) | 報告(クリーン) |
| 有効なJSONが必要 | 不要 | 必要 |
| 大きなファイルの速度 | 速い | 遅い(パースのオーバーヘッド) |
キー順序の差異を扱う
JSONオブジェクトはRFC 8259によれば順序なしのキーと値のペアのコレクションとして定義されています。実際には、異なる言語、シリアライザー、プリティプリンターが同じデータに対して異なるキー順序を生成します。
Pythonのjson.dumpsは挿入順でキーを出力します。JavaScriptのJSON.stringifyはモダンエンジンでは挿入順を維持しますが、以前の挙動は異なりました。Goのencoding/jsonは宣言順でstruct フィールドをシリアライズします。これらのシステムがデータをやり取りすると、生成されるJSONはセマンティックに同一ですがテキストとして異なります。
適切なJSON diffはキー順序を比較前に正規化するため、{"z":1,"a":2}と{"a":2,"z":1}は同一として報告されます。
テキストdiffを使用してキーの並び替えを処理したい場合は、diff前にキーをソートします。
import jsondef normalize(data):return json.dumps(json.loads(data), sort_keys=True, indent=2)# 正規化されたバージョンをテキストdiff
配列比較の課題
配列はJSON比較で最も難しい部分です。オブジェクトと異なり、配列は順序付きです。[1, 2, 3]と[3, 1, 2]は同じ要素を含んでいても意味的に異なります。これが2つの一般的な問題を生み出します。
挿入・削除によるシフト
配列の先頭や中間に要素が挿入されると、単純なdiffはインデックスがシフトするためその後のすべての要素を変更として報告します。インデックス0に1件挿入されただけなのに0〜50のアイテムすべてが変更されたと表示するdiffは役に立ちません。
より優れたdiffアルゴリズム(Myers diffなど)は共通のサブシーケンスを検出し、挿入と削除を正確に報告して誤検知を最小限にします。
オブジェクト配列内のアイテム識別
オブジェクトの配列では、優れたdiffは配列の位置ではなく意味のあるキー(idなど)で対応するアイテムを識別します。これにより、リスト内のアイテムの並び替えが削除と挿入のセットではなく移動として正しく報告されます。
// Before[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]// After(順序が入れ替わった)[{"id": 2, "name": "Bob"}, {"id": 1, "name": "Alice"}]
位置ベースのdiffは2つの変更を報告します。ID認識のdiffは変更なしと報告します。
開発でのJSON diff実践ヒント
比較前に正規化する
diff前に必ず両方のドキュメントを正規化します。キーのソート、数値フォーマットの正規化、装飾的な空白の除去を行います。これによりノイズが排除され、diff出力が意味のあるものになります。
CI/CDパイプラインでのJSON diff
CIパイプラインでJSON比較を自動化することで、APIコントラクトや設定ファイルへの意図しない変更がマージされる前に検出できます。
# シェルスクリプトでAPIレスポンスのスナップショットを比較npx json-diff expected.json actual.jsonif [ $? -ne 0 ]; thenecho "APIレスポンスが予期せず変更されました"exit 1fi
スナップショットテスト
JestなどのフレームワークはスナップショットテストにJSONシリアライゼーションを使います。スナップショットテストが失敗すると、出力は実質的にJSON diffです。行が変わったという事実ではなく、どのキーが変わったかに注目して構造的に読むことで、失敗したスナップショットの修正がはるかに速くなります。
// Jestスナップショットテスト — 失敗出力は読みやすいJSON diffexpect(apiResponse).toMatchSnapshot();
環境間の設定ドリフトの追跡
ほぼ同一であるべきステージングと本番の設定がある場合、定期的にdiffを取ることで意図しない環境固有のオーバーライドが明らかになります。
diff前に機密フィールドを削除する
デバッグやロギングのためにdiffを共有する場合、比較前に機密フィールドを削除します。トークン、パスワード、個人情報をプレースホルダー値に置き換える正規化ステップを構築します。
function redact(obj, keys) {const result = { ...obj };for (const key of keys) {if (key in result) result[key] = '[REDACTED]';}return result;}
JSON diff出力の読み方
標準的なJSON diffは色とシンボルで変更を示します。
| シンボル/色 | 意味 |
|---|---|
| 追加 — 新しいドキュメントにあり、古いドキュメントにはない |
| 削除 — 古いドキュメントにあり、新しいドキュメントにはない |
| 変更 — キーは両方にあるが値が変わった |
| シンボルなし | 変更なし — 両方のドキュメントで同一 |
diffを読む際は、変更されたフィールド(~)から始めます。これが通常最も興味深い部分です。追加と削除は明確です。何かが明示的に追加または削除されたことを示します。
まとめ
JSON diffはAPI開発、設定管理、データ監査に不可欠なツールです。テキストベースdiffは高速ですが、キーの並び替えやフォーマット変更によるノイズが生じます。構造的diffはJSONデータを比較するための正しいツールです。まずドキュメントを解析し、実際の値を比較します。
JSONKitのCompareツールはブラウザ上でサイドバイサイドの構造的JSON diffを提供します。2つのJSONドキュメントを貼り付けると、セットアップ不要で差分がインラインでハイライトされます。すべての比較はクライアントサイドで行われます。データはブラウザの中に留まります。