JSON比較ツール
入力した内容が保存・記録・共有されることはありません。
2つのJSONを貼り付けると、追加・削除・変更された値をパスごとに一覧表示します。キーの順序は無視されるため、実際の差分だけが報告されます。
使い方
- 左側に元のJSONを貼り付けます。
- 右側に比較したいJSONを貼り付けます。
- 差分がパス付きで下に表示されます。
比較のしくみ
両方のドキュメントを解析し、ルートから並行してたどりながら、左右で同じパスにあるものを比較します。オブジェクトはキーで対応づけるのでキーの順序を入れ替えても何も変わらず、配列はインデックスで対応づけます。すべての差分は3種類のいずれかで報告されます。「追加」はそのパスが右側にだけ存在すること、「削除」は左側にだけ存在すること、「変更」は両側にそのパスの値があるものの値が異なることを意味します。
型が変わった場合も変更として扱われ、これが現れたら注意深く見る価値があります。数値の42だったフィールドが文字列の"42"になっていても、寛容なクライアントではたいてい動き続け、その値で計算する下流の処理で初めて失敗します。オブジェクトだったフィールドが、要素1つの配列に変わった場合も同様です。条件に応じてレスポンスを組み立てるコードでよく起きる事故です。
配列をインデックスで比較する理由
表示される結果に最も影響する設計上の判断なので、はっきり書いておきます。200要素の配列の先頭に1つ要素を挿入すると、挿入位置より後ろのすべての要素のインデックスがずれるため、インデックスによる比較では200件の変更が報告されます。何かが壊れているわけではなく、差分があなたの想定とは別の問いに答えているだけです。
代わりに最長共通部分列を探して挿入として報告する方法もあり、テキストの行単位diffはそうしています。しかしJSONではそれが明らかに優れているとは言えません。リスト内の2つのオブジェクトが同じものかどうかを判断する必要があり、それにはあなたが指定しておらずツールにも推測できないキーが必要です。推測を誤れば、自信たっぷりで誤解を招く答えを出してしまいます。インデックスによる比較は大ざっぱですが、存在しない対応関係をでっち上げることは決してありません。
実際にリストの並び順が変わっている場合は、比較する前に両側を識別子で並べ替えてください。
null、存在しないキー、パスの読み方
一方ではnullの値で存在し、もう一方ではまったく存在しないキーは、変更ではなく追加または削除として報告されます。この2つは異なる状態で、コードでの扱いも異なるからです。存在しない場合はたいていそのフィールドが一度も設定されていないことを、nullの場合はたいてい意図的に「値なし」が設定されたことを意味します。一方をもう一方の意味で使うAPIはバグの原因になり、実際にどちらを送っているのかを示してくれるのがこのツールです。
パスはルートからの経路として読みます。user.roles[2]は、userオブジェクトの中にあるroles配列の3番目の要素です。この記法は、JavaScriptでその値にアクセスするときに書くものと意図的に同じにしてあり、JSONPath式にも十分近いので、JSONPathを受け付ける多くのツールにそのまま貼り付けられます。
本当の変更だけを見つけるリグレッション手順
JSON差分の最も一般的な用途は、デプロイによって変わってはいけない部分が変わっていないことを確認することです。現行バージョンのレスポンスを取得し、候補バージョンからも同じものを取得して比較します。難しいのは比較そのものではなく、常にノイズです。レスポンスには毎回正当に変化するフィールドが含まれているのが普通だからです。
比較の前に、そうしたフィールドを取り除くか正規化しておきます。自動生成されるID、タイムスタンプ、リクエストID、有効期限付きの署名URL、処理時間を表すものなどです。ドキュメントを走査してこの種のフィールドを固定のプレースホルダーに置き換える短いスクリプトを書けば、200件の差分が本当に重要な1件だけの差分になります。
APIが順序を保証していない箇所についても同じことをします。タグのリストが呼び出しごとに違う順序で返ってくるなら、まず両側を安定したキーでソートしてください。そうしないと、インデックスベースの比較はすべての要素を変更扱いにしてしまい、唯一の本物の差分がその中に埋もれてしまいます。
等しい、同一、そして等価
2つのドキュメントが「等しい」と言えるかどうかには複数の意味があり、ツールはそのどれか1つを選ばなければなりません。バイト単位の一致は最も厳格ですが、整形し直しただけで崩れるため実用性は最も低いものです。空白やキーの順序に関係なく、同じキーが同じ値を持つという構造的な一致こそ、この比較が行っているものです。そして3つ目の意味である意味的な等価性は、スキーマに依存します。
構造的な比較が厄介になるのは数値です。JSONは整数と浮動小数点数を区別しないため、パーサーにとって1、1.0、1e0はすべて同じ値です。どちらの形で出力するかはライブラリのバージョンによって変わることがあり、実質的には何も変わっていないのに表現だけが変わります。末尾のゼロが往復の過程で消えるのも同じ理由です。
文字列は、見た目が同じでもバイト列が異なることがあります。Unicodeでは同じ文字を複数の方法で表現できるからです。アクセント付きの文字は1つのコードポイントで表すことも、基底文字と結合記号の組み合わせで表すこともできます。比較の前にNFCへ正規化すれば、この種の偽の差分はなくなります。複数のソースからテキストが集まってくる場面では、必ずやっておく価値があります。
差分ツールが適していない場面
2つの例を比較して分かるのは、その2つの例についてだけです。レスポンスがルールに適合しているかを確かめるのは別の仕事であり、そのための道具はスキーマです。ドキュメントならJSON Schema、エンドポイントならOpenAPIです。スキーマはidが整数であること、emailが特定のパターンに一致することを宣言し、たまたま採取した1組ではなくすべてのレスポンスに対して効力を持ちます。
実務では両者を組み合わせて使います。CIでのスキーマ検証は、型が変わったフィールドや消えた必須プロパティといった構造の逸脱を捕まえます。一方、差分は調査の場面で、この特定の2つのペイロードの間で何が変わったのかという、より限定的な問いに答えます。
コントラクトテストはさらに一歩進み、コンシューマー側が依存している内容を公開します。これにより、プロデューサー側のパイプラインは、古いサンプルと違うというだけでなく、実際のクライアントを壊す変更が入ったときに失敗するようになります。
パスを読んで、その場所へ飛ぶ
ここで表示されるパスは、ルートからの経路として読めます。user.roles[2]は、userオブジェクトの中のroles配列の3番目の要素という意味です。これは、その値にたどり着くためにJavaScriptで実際に書く式と同じ形にしてあります。ドキュメントを変数に入れておけば、パスをそのままコンソールに貼り付けられます。
同じ形は、ログツールやAPIツールの多くが受け付けるクエリ言語であるJSONPathにも近いものです。JSONPathでは式がドル記号で始まり、$.user.roles[2]となります。コマンドラインプロセッサーのjqは、ドル記号のない同様の記法を使うので、.user.roles[2]でファイルやストリームから同じ値を取り出せます。
非常に大きなドキュメントでは、差分そのものよりもこのパスのほうが役に立つ出力になります。差分がどこにあるか分かってしまえば、jqやHTTPクライアントのフィルターで両側からその部分木だけを取り出せます。ドキュメント全体を眺めるどんな方法よりも、そのほうが速く読めます。
読み切れないほど大きなドキュメントを比較する
数メガバイトのレスポンスは、分析データのエクスポートやバルク系のエンドポイントではよくあることで、それを丸ごと比較しても誰にも使えない結果しか出てきません。有効なのは、比較の前に減らすことです。関心のある部分木を選び、話題になっているフィールドだけに絞り、コレクションは識別子でソートします。
最初の一手としては件数を数えるのが有効です。片方に4,812件、もう片方に4,811件あるなら、知りたいのはどのレコードが欠けているかであって、これはドキュメントの差分ではなく識別子の集合比較です。ソート済みのID一覧を2つ用意して行単位の差分を取れば、数秒で答えが出ます。
ペイロードが本当に大きく、比較を繰り返し実行する必要があるなら、ブラウザよりもコマンドラインのパイプラインが優れています。jqで両方のファイルをソート済みのコンパクトな行に正規化し、diffにかけます。どちらの方法でもすべてローカルで動きますが、コマンドラインには応答性を保つべきページがありません。
よくある質問
- キーの順序は比較結果に影響しますか?
- いいえ。オブジェクトは位置ではなくキーで比較されるため、順序を入れ替えても差分にはなりません。配列は順序に意味があるため、インデックスで比較されます。
- データはサーバーに送信されますか?
- いいえ。比較処理はすべてブラウザ内のJavaScriptで実行されます。どちらのデータも端末外に出ないため、本番のAPIレスポンスも安全に比較できます。
- user.roles[2] のようなパスは何を意味しますか?
- 差分の位置を示しています。userオブジェクト内のroles配列の3番目の要素という意味です。ドットはオブジェクトのキー、角かっこは配列のインデックスを表します。
- null とキーが存在しない場合の違いは?
- 区別して報告されます。片方にキーがありnullで、もう片方にキー自体が存在しない場合は「変更」ではなく「追加」または「削除」として表示されます。
最終更新:
操作手順、期待した結果、実際の結果を記載してください。秘密情報や個人情報は削除してください。
