JSON Diffデバッグガイド:複雑なデータの変更を発見する
標準的なテキストdiffツールはJSONに対して力不足です。キーの並び替え、空白の変更、深いネストにより、実際のデータ変更を覆い隠すノイズの多いdiffが生成されます。構造的JSON diffはテキスト表現ではなく、セマンティクス — 実際のデータ — を比較します。このガイドでは、効果的なJSON比較とデバッグの技術を紹介します。
標準的なDiffがJSONに失敗する理由
意味的に同一の2つのJSONオブジェクトを考えてみましょう:
バージョンA:
{"name":"Alice","age":30,"roles":["admin","editor"]}
バージョンB:
{
"age": 30,
"name": "Alice",
"roles": ["admin", "editor"]
}
テキストdiffはすべての行が変更されたと表示しますが、データは同一です。構造的diffは同一であることを認識します。
実際の変更を考えてみましょう:
// 変更前
{ "user": { "name": "Alice", "permissions": ["read", "write", "admin"] } }
// 変更後
{ "user": { "name": "Alice", "permissions": ["read", "write"] } }
構造的diffは removed user.permissions[2]: "admin" と報告します — まさに必要な情報です。
JSON Diffツールで即座にお試しください。
JSON変更の種類
構造的JSON diffは変更を以下のように分類します:
| 変更タイプ | 例 |
|---|---|
| 追加 | 新しいキーまたは配列要素 |
| 削除 | 欠落したキーまたは配列要素 |
| 変更 | 既存キーの値が変更 |
| 型変更 | 値の型が変更(string → number) |
| 移動 | 配列要素の並び替え |
コマンドラインJSON Diff
jqによるクイック比較
# キーをソートして比較
diff <(jq -S . before.json) <(jq -S . after.json)
# 特定のパスを比較
diff <(jq '.config.database' before.json) <(jq '.config.database' after.json)
専用ツール
# json-diff (npm)
npx json-diff before.json after.json
# 出力:
# {
# "user": {
# "permissions": [
# "read",
# "write",
#- "admin"
# ]
# }
# }
# jd (Go)
jd before.json after.json
APIレスポンスの変更をデバッグ
APIレスポンスが予期せず変更された場合、体系的な比較が根本原因の特定に役立ちます:
ステップ1:ベースラインのキャプチャ
既知の正常なレスポンスを保存:
curl -s https://api.example.com/users/123 | jq -S . > baseline.json
ステップ2:現在のキャプチャ
curl -s https://api.example.com/users/123 | jq -S . > current.json
ステップ3:構造的Diff
# 人間が読める形式
npx json-diff baseline.json current.json
# 機械が読める形式(JSON Patchフォーマット)
npx json-diff baseline.json current.json --json
ステップ4:ノイズのフィルタリング
リクエストごとに変わるフィールド(タイムスタンプ、リクエストID)を除外:
# 比較前に揮発性フィールドを削除
jq 'del(.meta.requestId, .meta.timestamp)' response.json
JSON Patch(RFC 6902)
JSON PatchはJSONドキュメントへの変更を記述するための標準化されたフォーマットです:
[
{ "op": "replace", "path": "/user/name", "value": "Bob" },
{ "op": "remove", "path": "/user/permissions/2" },
{ "op": "add", "path": "/user/email", "value": "bob@example.com" }
]
操作:
add: 新しい値を追加remove: 値を削除replace: 既存の値を変更move: 値を新しいパスに移動copy: 値を新しいパスにコピーtest: 値が存在するか確認(条件付きパッチ用)
JSON Patchはドキュメント全体を置換する代わりにAPIに増分更新を送信するのに便利です。
設定のドリフト検出
設定ファイルの経時変化を追跡:
#!/bin/bash
# drift-check.sh
BASELINE="config-baseline.json"
CURRENT="config-current.json"
DIFF=$(npx json-diff "$BASELINE" "$CURRENT" 2>/dev/null)
if [ -n "$DIFF" ]; then
echo "Configuration drift detected:"
echo "$DIFF"
exit 1
fi
echo "No drift detected."
テストでのJSON Diff
テストで構造的比較を使用してAPIレスポンスを検証:
const { diff } = require('json-diff');
test('API response matches expected structure', async () => {
const response = await fetch('/api/users/123');
const data = await response.json();
const expected = {
id: 123,
name: 'Alice',
role: 'admin'
};
const changes = diff(expected, data);
expect(changes).toBeUndefined(); // undefinedは同一を意味
});
大きなJSONファイルの処理
数メガバイトのJSONファイルでは、ビジュアルdiffは非実用的になります:
- 先にクエリ: diffの前にJSONPathで関連セクションを抽出。JSONPathガイドを参照。
- サマリーモード: すべての変更を表示するのではなく、タイプ別に変更数をカウント
- ストリーミングdiff:
jdのようなツールはストリーミングで大きなファイルを効率的に処理
# 特定のセクションを抽出して比較
jq '.data.users[:10]' large-before.json > section-before.json
jq '.data.users[:10]' large-after.json > section-after.json
npx json-diff section-before.json section-after.json
FAQ
JSON PatchとJSON Merge Patchのどちらを使うべきですか?
JSON Patch(RFC 6902)は操作の配列を使用し、配列要素の操作を含むあらゆる変更を表現できます。JSON Merge Patch(RFC 7396)はよりシンプルで、部分的なJSONオブジェクトを送信してターゲットとマージします。シンプルなオブジェクト更新にはMerge Patchを、配列操作やアトミック操作が必要な場合はJSON Patchを使用してください。
キーの順序が異なるJSONをdiffするにはどうすればよいですか?
ほとんどの構造的JSON diffツールはキーの順序を無視します — {"a":1,"b":2} は {"b":2,"a":1} と等しいです。テキストベースのツールでは、キーをアルファベット順にソートする jq -S . で先に正規化します。JSON Diffツールはキーの順序を自動的に処理します。
関連リソース
- JSON Diff — JSONドキュメントを並べて比較
- JSONフォーマッター — 視覚的な検査のためにJSONをフォーマット
- テキストDiff比較ガイド — 一般的なテキスト比較テクニック