JSON 差分

二つの JSON 文書を比較し、追加・削除・変更を確認できます。キーの順序、インデント、圧縮の有無は無視され、データはブラウザから出ません。

1 つ目の JSON
2 つ目の JSON
結果

両方のパネルに JSON を貼り付けると比較します

書き方ではなくデータを比べる

行ごとの比較が答えるのは書き方についての問いです。すなわち、このファイルのどの行があちらのファイルの行ではないか、ということ。文章やソースコードにはそれが正しい問いで、JSON には誤った問いです。JSON 文書には唯一の正しい書き方というものがないからです。オブジェクトのメンバーを並べ替え、インデントを変え、片方を圧縮すれば、行はすべて変わるのにデータはまったく動きません。このツールは二つの文書が解析された結果どうなるかを比べるので、報告されるのは実際に変わったものです。下は同じ文書を二度書いたものです。一行目は圧縮したもの、続いて整形し、メンバーを別の順序に並べたものです。

{"name":"checkout","port":8080,"tags":["a","b"]}

{
  "port": 8080,
  "tags": ["a", "b"],
  "name": "checkout"
}

この二つは一行も共有していませんが、このツールは両者のあいだに差分をまったく報告しません。メンバーも値も同じで、配列の中の順序も同じだからです。実際上の帰結として、比較を成り立たせるために文書を整形し直す必要は決してありません。それをすれば、比べようとしていたそのものを変えてしまうことになります。

差分として数えないもの

文書の書き方に関する四つのことは無視されます。しかも無条件に無視されます。そのどれかを戻すつまみはありません。それができるツールは、説明すべき意味を二つ持つ二つのツールになってしまい、読む人は目の前の答えがどちらから出たものかを知っていなければならなくなるからです。

  • オブジェクトのメンバーが書かれている順序。{"a": 1, "b": 2} と {"b": 2, "a": 1} は同じデータを持つので、実行のたびに異なる順序でキーを直列化するプログラムがここで変更として現れることはありません。
  • あらゆる種類の空白 — インデント、改行、コロンのあとの空白。同じデータを持つ圧縮された文書と整形された文書は、同じ文書が二度あるだけです。
  • 数値の書き方。1e3 は 1000 であり、1.50 は 1.5 です。JSON の数値には値があって綴りはないからで、そこだけが違う二つの文書は同じ数値を持ち、それについては何も報告されません。
  • 文字列の中の文字のエスケープの仕方。六文字の \u0041 と文字 A は、同じ一文字の文字列です。

その一覧に入っていそうで、あえて入っていないものが二つあります。配列の要素の順序はデータであり([1, 2] と [2, 1] は別の文書です)、値が null のメンバーは、そもそも存在しないメンバーとは同じではありません。ですから一方に対する他方は、黙って見過ごされるのではなく報告されます。

追加・削除・変更 — そして型の変化

結果の各行はちょうど三種のいずれか一つであり、三つ合わせて比較が見つけうるすべてを覆います。次の二つの文書を見てください。

{"host":"api.example.com","port":8080,"debug":true}

{"host":"api.example.com","port":"8080","retries":3}
  • $['port'] で変更。メンバーは両方の文書にあり値が同じでないので、両方の値が示されます。この行はさらに型の変化としても印が付きます。8080 は数値で "8080" は文字列だからです。
  • $['debug'] で削除。メンバーは一つ目の文書にあり、二つ目にはありません。
  • $['retries'] で追加。メンバーは二つ目の文書にあり、一つ目にはありません。

host という名のメンバーは両方に同じ値であるので、まったく報告されません。型の変化は変更の性質であって四つ目の行の種類ではありません。スキーマは気づくのに二つのファイルを読む人はたいてい気づかない、その食い違いこそが型の変化なので、あなたに見つけさせるのではなく起きた場所で言い添えられます。

差分のある場所を、パスとして書く

各行は自分のある場所を名指し、それを行番号ではなく文書の中へのパスとして名指します。行番号は書き方についての事実であり、このツールがたった今無視し終えたものだからです。パスは RFC 9535 の正規化パスで、JSON の中の位置を書き留める必要があるところではこのサイトが常に用いる唯一の書き方です。

  • $ は文書全体、$['port'] はその中の port という名のメンバー、$['hosts'][1] は hosts の下の配列の添字 1 の要素です。メンバーは常に角かっこと引用符で書かれるので、点や空白や自分のかっこを含む名前でも特別なことは要りません。
  • パスは実行できるクエリです。属する文書といっしょにこのサイトの JSONPath テスターに貼り付ければ、まさにそのノードが選ばれます。差分を周囲ごと読むにはこれがいちばん早い方法です。
  • それはノードが実際に入っているほうの文書に対するクエリです。追加されたメンバーのパスは二つ目の文書の上で走り、削除されたメンバーのパスは一つ目の上で走ります。ここでは存在しないノードが名指されることはありません。

変更の行は二つのパスを持つことがあります。それは次の節の対応付けが透けて見えているのです。配列が要素を得たり失ったりした時点で、同じ要素が左右で別の添字に座ることになります。両方のパスが同じところ — ほとんどの場合そうです — では、片方だけが示されます。

二つの配列をどう対応付けるか

二つのオブジェクトはメンバーごとに比較され、それは簡単です。メンバーには名前があるからです。二つの配列には名前がなく位置しかありません。そして位置ごとに比べることこそ、たいていの比較結果を読めなくしているものです。長い配列の先頭にレコードを一つ差し込めば、もう何ひとつ元の場所にはなく、位置を同一性として読む比較は配列全体を違うと報告します。狭い意味では正しく、それ以外のあらゆる意味で役に立ちません。ですから要素はまず対応付けられます。あなたが指定する項目によってではなく、要素が何であるかによってです。そして対応付けが余らせたものだけが報告されます。

{"hosts": ["alpha", "beta", "gamma"]}

{"hosts": ["alpha", "staging", "beta", "gamma"]}

これは一行です。staging が $['hosts'][1] に追加されました。beta と gamma という要素は一つずつずれただけで、触れられません。動くことは変わることではないからです。同じ対応付けのおかげで、配列の一項目の中で編集された一つのフィールドも、そのフィールドにおける変更として報告され、要素まるごとが別のものに差し替えられたとは報告されません。

順序はやはり数に入りますし、対応付けもそうでないふりはしません。[1, 2] と [2, 1] は、先頭から 1 が削除され 2 のあとに 1 が追加された、と返ってきます。実際に起きたのはそれです。二つの配列は、対応付けが割に合わないほど違っていることもあります。その点を越えると比較は位置ごとの走査に戻り、そのことを結果の先頭で述べます。騒がしい答えが謎ではなく説明済みになるわけです。

レポートとツリー

同じ比較が二つの見せ方で提供され、切り替えても何も計算し直されません。ツリーはレポートが並べるまさにその差分の一覧から描かれるので、何が変わったかについても、どれだけあるかについても、二つが食い違うことはありません。

  • レポートは「何が変わったか」に答えます。差分ごとに一行で、それぞれがパスと種類と左右それぞれの値を持ちます。一覧を丸ごと目の前に置きたいときに眺める見せ方です。
  • ツリーは「文書のどこか」に答えます。二つの文書を一つの見出し構造に融かし、差分を持つ部分だけを開いた形です。何も持たない子が連続する区間は、いくつ分かを述べる一行に畳まれます。前の節の対はこう描かれます。文書、その中の配列、一要素分の畳まれた行、追加された要素、そして二要素分の畳まれた行の五行です。
  • 結果の見出しの横の件数は、存在する差分すべてです。画面の一覧が長さのために切り詰められてもその件数は動かず、一覧の下のつまみが残りを見せます。
~! $['port'] 8080 -> "8080"
- $['debug'] true
+ $['retries'] 3

三つ目の節の二つの文書に対して、コピーのつまみが作るのがこれです。種類を表す印、続いてパス、続いて矢印を挟んだ値 — 追加されたメンバーにはプラス、削除されたメンバーにはマイナス、変更にはチルダ、型まで変わったところにはチルダと感嘆符です。言葉ではなく記号で書かれているのはわざとで、貼り付けた結果が、サイトのどの言語で読んでいたとしても同じ文字列になるようにするためです。そして何も短くしません。画面が切り詰めた値も、そこには丸ごと入っています。

解析の代償と、ツールが声に出して言うこと

二つの文書が解析された結果を比べるとは、解析の代償とともに生きることであり、JSON の解析は無損失ではありません。それがこのサイトのどこよりもここで重いのは、比較ツールがいちばん出してはならない答えが、実際には違う二つの文書に対する「差分なし」だからです。ですから各パネルのテキストはもう一度テキストとして読まれ、解析が落としたものは飲み込まれずに比較の隣で報告されます。

  • 同じオブジェクトの中に二度書かれたメンバー名。解析を生き延びるのはあとのコピーだけなので、比較されたのもあとのコピーだけであり、先のものは誰にも見られていません。
  • 倍精度として生き延びるには長すぎる数値。異なる二つのリテラルが同じ値に解析されることがあり、識別子はまさにそれが起きる場所です。12345678901234567890 と 12345678901234567891 は別の整数でありながら解析後は一つの数値なので、比較だけに任せれば、識別子の異なる二つの文書を同一と呼んでしまいます。
{"user": {"id": 1}, "user": {"id": 2}}

この文書は {"user": {"id": 2}} に解析され、ツールはそれを、貼り付けられたパネルの下に、最初の箇所の行と桁および全部でいくつあるかとともに述べます。どちらの損失も比較を止めません。どちらも比較の隣に並びます。まったく解析できない文書も同じ場所で報告され、示せる位置があるかぎり問題の位置も添えられます。二つのパネルは互いに独立して読まれるので、壊れた文書が二つあれば、片方ずつではなく二つのエラーが同時に出ます。値を描ける深さよりもはるかに深く入れ子になった文書はその場で拒まれます。それは拒絶であって、固まったタブではありません。

貼り付けたものがこのタブから出ない理由

二つの文書はどちらもあなたのブラウザに留まります。アップロードもリクエストも、写しを持ちうるサーバーもありません。比較はすでに読み込まれているページの中で走る算術であり、だからこそネットワークを切っても動き続けます。本番の応答や顧客の記録、署名済みのペイロードを貼り付けても安全なのはそのためです。

保存もされません。ページを再読み込みすれば両方のパネルは空です。履歴もアカウントもなく、あとから消すものもありません。ここから出ていくのは、あなた自身がコピーしたものだけです。

よくある質問

キーの順序は無視されますか。
はい、常に無視されます。そしてそれこそがこのツールの存在理由です。同じメンバーを同じ値で持つ二つのオブジェクトは、どう書かれていても同一なので、実行のたびに異なる順序でキーを直列化するプログラムが変更として現れることはありません。インデント、圧縮、数値の書き方、文字のエスケープの仕方が無視されるのも同じ理由で、そのどれもあなたが逆に切り替えられた設定ではありません。
null が入ったメンバーと、存在しないメンバーは同じですか。
いいえ、そして両者を同じ扱いにする選択肢もありません。{"note": null} と {} の比較は $['note'] の削除として報告されます。存在して空である項目は、誰も送らなかった項目とは別の主張です。どのスキーマ言語も、静的型付けのどの言語も両者を区別します。ですからここで一緒くたにすれば、文書が現に言っていることを捨てることになります。
[1, 2] と [2, 1] は同じと報告されますか。
いいえ。JSON の配列は順序を持つので、同じ要素を別の順序で持つ二つの配列は別の文書であり、等しいと呼べばこのツールが形式について嘘を言うことになります。返ってくるのは削除一つと追加一つです。1 が先頭から削除され、2 のあとに再び追加されました。オブジェクトは逆です。そのメンバーは位置ではなく名前で指されるからです。
各行のパスは何を意味しますか。
差分が文書の中のどこにあるかを、RFC 9535 の正規化パスとして書いたものです。$ は文書そのもの、$['port'] はそのメンバー、$['hosts'][1] は hosts の下の配列の添字 1 の要素です。実行できるクエリでもあります。属する文書といっしょにこのサイトの JSONPath テスターに貼り付ければ、まさにそのノードだけが選ばれます。
ある行に二つの違うパスが出たのはなぜですか。
二つの配列を対応付けると、同じ要素が左右で別の添字に残ることがあるからです。["alpha", "beta"] と ["intro", "alpha", "beta!"] を比べると二行出ます。intro が $[0] に追加され、beta が beta! に変わりました。後者は一つ目の文書では $[1]、二つ目では $[2] です。両方のパスが同じところでは、そしてたいていは同じですが、片方だけが示されます。
行にある型の変化とはどういう意味ですか。
値が単に違うのではなく、別の種類のものになったという意味です。数値が文字列になった、値が null になった、オブジェクトが配列になった、というように。片方で 8080、もう片方で "8080" と書かれたポートがいちばんよくある例で、たいていはデータではなくシリアライザーかクライアントが変わったことを意味します。行に印が付くのは、編集された値と、ずれていく取り決めとでは、あなたに求められる対応が違うからです。
二つの文書は同じに見えるのに、片方が同じキーを二度書いています。どうなりますか。
解析はあとのコピーを残し、先のものを落とします。ですから比較は一つのメンバーしか見ず、差分なしと報告することも十分にありえます。ツールがまさにそのために生のテキストを別途読み、そのパネルの下で、名前が二度書かれていること、全部で何回あるか、最初がどこかを述べます。この同じ二度目の読みが、倍精度として生き延びるには長すぎる整数も捕まえます。違う二つの文書が違わない二つに解析されうる、もう一つの道筋です。
適用できるパッチをもらえますか。
いいえ。それは欠けているのではなく決めたことです。JSON Patch は一つずつ操作を適用していくので、中の添字は実行しながらずれていきます。それを出力するのは、あなたが自分のデータに対して走らせるプログラムについての約束であり、何が違うかを述べることよりもずっと大きな主張です。代わりに持ち帰れるのは、コピーのつまみから得られるテキストのレポートです。サイトのどの言語でも同じで、どこも短くされていません。
貼り付けたものはサーバーに送られますか。
いいえ。二つの文書はあなたのブラウザの中で比較され、どちらもそこから出ません。アップロードもリクエストもなく、訪問のあいだに残るものもありません。本番の応答を貼り付けても安全なのはそのためで、ネットワークを切ってもツールが動き続けるのもそのためです。ページを再読み込みすれば両方のパネルはまた空になります。

関連するツール

  • テキスト差分

    見たい変更がデータではなく書き方のほうにあるとき — インデントのかけ直し、位置の動いたキー、そもそも JSON ではない二つのファイル — それを見せてくれるのは行ごとの比較です。あちらのページがするのはまさにそれで、空白・大小文字の扱いと、コピーできるパッチが付いています。

  • JSON フォーマッター

    JSON を検証して整形 — エラー位置も明示。

  • SQL フォーマッター

    SQL の整形と美化 — 複数の方言に対応。

  • XML フォーマッター

    XML の整形と整形式チェック — 美化も圧縮も。