Jak porównać dwa pliki JSON i znaleźć zmiany

Najszybszym sposobem na porównanie dwóch plików JSON jest wklejenie obu do narzędzia diff z widokiem obok siebie, sformatowanie ich w ten sam sposób i odczytanie podświetlonych linii. Trudną częścią zwykle nie jest samo porównywanie. To szum: zmieniona kolejność kluczy, różne wcięcia i zbędne końcowe przecinki mogą sprawić, że dwa prawie identyczne pliki wyglądają, jakby nie miały nic wspólnego.

Ten przewodnik opisuje, jak uzyskać czysty i wiarygodny diff. Przyjrzymy się, dlaczego pliki JSON rozchodzą się na papierze, pozostając takie same w znaczeniu, kilku metodom wartym poznania oraz przepracowanemu przykładowi, który możesz śledzić. Jeśli potrzebujesz tylko narzędzia, nasza strona porównania JSON robi to wszystko w przeglądarce.

Dlaczego porównywanie plików JSON jest zwodniczo trudne

JSON ma małą, ścisłą gramatykę (zob. specyfikację na json.org), ale daje autorom dużą swobodę w układaniu tekstu. Dwa pliki mogą opisywać dokładnie ten sam obiekt i nadal różnić się bajt po bajcie. Zwykły diff tekstowy nic o tym nie wie, więc skrupulatnie oznacza wszystko.

Oto rzecz, którą należy przyswoić przed rozpoczęciem: klucze obiektów w JSON nie mają kolejności. Specyfikacja (RFC 8259) definiuje obiekt jako nieuporządkowany zbiór par nazwa/wartość. Zatem {"name":"Ada","id":7} i {"id":7,"name":"Ada"} są równe, choć diff liniowy pokoloruje je na czerwono i zielono.

Wygląda jak zmiana, zazwyczaj nią nie jest
Co widzisz w diffieCzy to prawdziwa zmiana?Co zrobić
Klucze w innej kolejnościNie, obiekty są nieuporządkowanePosortuj klucze po obu stronach
Wcięcie 2 vs 4 spacjeNieSformatuj obie strony tak samo
Skrócony vs ładnie sformatowanyNieSformatuj obie strony
Końcowy znak nowej linii na końcu plikuNieZignoruj lub przytnij białe znaki
Wartość zmieniona z "7" na 7Tak, ciąg vs liczbaZbadaj, to jest prawdziwe
Elementy tablicy w innej kolejnościMoże, tablice uporządkowaneZdecyduj, czy kolejność ma tu znaczenie

Ten ostatni wiersz zaskakuje ludzi. Tablice zachowują kolejność, obiekty nie. Dlatego [1, 2, 3] i [3, 2, 1] są naprawdę różne, ale klucze wewnątrz obiektu mogą się dowolnie przestawiać. Jeśli chcesz poznać szczegóły dotyczące tego, jak JavaScript to wszystko przetwarza, MDN ma solidny opis obiektu JSON.

Cztery sposoby porównywania JSON i kiedy po który sięgać

Nie ma jednej najlepszej metody. Zależy od tego, gdzie znajdują się pliki i czego chcesz się dowiedzieć. Oto zestawienie popularnych opcji.

MetodaNajlepsza doWysiłekRozumie JSON?
Przeglądanie wzrokiemMałe pliki, jedno lub dwa polaNiskiNie, parserem jesteś ty
Narzędzie diff onlineSzybkie sprawdzenia, wklejanie skądkolwiekNiskiZ formatem + sortowaniem kluczy, tak
Wiersz poleceń (jq, diff)Pliki na dysku, skryptowanie, duże plikiŚredniTak, gdy najpierw posortowane
IDE lub git diffPliki już w repozytoriumNiski, jeśli zacommitowaneLiniowy domyślnie

Dla większości osób narzędzie przeglądarkowe wygrywa szybkością, bo nie trzeba nic instalować i można wkleić fragment prosto z logu lub wywołania API. Problemem jest szum formatowania, którym zajmiemy się dalej. Jeśli żyjesz w terminalu, jq to narzędzie do opanowania i pokażemy jedną flagę, która ma znaczenie.

Najszybsze czyste porównanie krok po kroku

To rutyna, której używam, gdy ktoś podaje mi dwa pliki konfiguracyjne i pyta „co się różni?". Zajmuje około piętnastu sekund.

  1. Otwórz narzędzie do porównywania JSON.
  2. Wklej oryginał po lewej, nową wersję po prawej.
  3. Kliknij Formatuj po obu stronach, żeby używały tego samego wcięcia.
  4. Włącz sortowanie kluczy (kanonizuj), żeby zmieniona kolejność kluczy przestała być wyświetlana jako zmiana.
  5. Odczytaj wynik. Zielony oznacza dodane, czerwony usunięte, a zmieniona wartość jest pokazana jako jedno i drugie.

Kroki trzeci i czwarty to cały trik. Gdy oba pliki są identycznie sformatowane i ich klucze posortowane, jedyne, co pozostaje do podświetlenia, to to, co naprawdę się zmieniło. Nasz silnik diff jest zbudowany na bazie Google'owego diff-match-patch, który porównuje najpierw linia po linii, dzięki czemu pozostaje szybki nawet na długich plikach.

Przepracowany przykład

Powiedzmy, że przeglądasz zmianę w rekordzie użytkownika. Oto stan przed:

{
  "name": "Ada Lovelace",
  "role": "editor",
  "active": true,
  "seats": 3
}

A oto stan po, tak jak przekazał ci go kolega z zespołu:

{
  "active": true,
  "name": "Ada Lovelace",
  "role": "admin",
  "seats": 5,
  "team": "platform"
}

Wrzuć je do surowego diffu liniowego, a wygląda, jakby prawie każda linia się przesunęła, bo klucze są w innej kolejności. Sformatuj i posortuj oba, a prawdziwa historia jest krótka:

Co naprawdę się zmieniło
PolePrzedPoZmiana
roleeditoradminZmieniono
seats35Zmieniono
teamplatformDodano
nameAda LovelaceAda LovelaceBez zmian
activetruetrueBez zmian (tylko przesunięte)

Trzy prawdziwe edycje: awans roli, liczba miejsc i nowe pole zespołu. Zmiana kolejności to był szum. Ta promocja z editor do admin to właśnie coś, co chcesz wychwycić podczas przeglądu, i łatwo to przeoczyć, gdy jest zagrzebane pod dwudziestoma liniami fałszywych alarmów.

Eliminowanie szumu formatowania w wierszu poleceń

Jeśli twoje pliki są już na dysku, ta sama idea „formatuj i sortuj" działa za pomocą dwóch krótkich poleceń. Flaga -S mówi jq, żeby sortował klucze obiektów; przepuszczenie przez nią obu plików normalizuje je tak, że zwykły diff jest uczciwy:

jq -S . old.json > old.sorted.json
jq -S . new.json > new.sorted.json
diff old.sorted.json new.sorted.json

Teraz diff raportuje tylko wartości, które naprawdę się zmieniły, ponieważ oba pliki mają to samo wcięcie i tę samą kolejność kluczy. To odpowiednik kliknięcia Formatuj i sortuj klucze w przeglądarce.

Diff tekstowy a diff strukturalny

Wszystko powyżej to diff tekstowy: szybki, wizualny i doskonały dla osoby czytającej zmianę. Diff strukturalny idzie dalej i opisuje zmianę jako dane. Standardem jest JSON Patch, zdefiniowany w RFC 6902, który wyraża edycje jako operacje takie jak replace i add na danej ścieżce. Diffu strukturalnego potrzebujesz, gdy program ma zastosować zmianę, a nie tylko osoba na nią patrząca. Na co dzień diff tekstowy z posortowanymi kluczami w zupełności wystarcza.

Częste pułapki, na które należy uważać

PułapkaDlaczego gryzieRozwiązanie
Precyzja liczbLiczby całkowite powyżej 2^53 − 1 tracą precyzję w JavaScriptPorównuj duże ID jako ciągi; zob. MAX_SAFE_INTEGER
Sekwencje ucieczki Unicode"café" i "café" to ten sam ciąg, różne bajtySformatuj obie strony, co normalizuje kodowanie
Zduplikowane kluczeWiększość parserów cicho zachowuje ostatniZwaliduj JSON przed zaufaniem diffowi
Końcowe przecinkiNie są legalnym JSON; jeden plik może w ogóle nie zostać sparsowanyNajpierw napraw składnię, walidator to oznaczy
Ciąg vs liczba"5" i 5 wyglądają podobnie, ale są różnych typówTo jest prawdziwa zmiana, nie ignoruj jej

Powiązane narzędzia

JSON rzadko jest jedynym formatem, z którym masz do czynienia. Jeśli porównujesz konfigurację między środowiskami, porównywanie YAML stosuje tę samą ideę do YAML. Przeglądanie zmian między dwoma wywołaniami API to zadanie, do którego stworzono narzędzie do diffowania odpowiedzi API, a aktualizacje zależności najłatwiej odczytać na stronie diff package.json.

Często zadawane pytania

Czy porównywanie plików JSON online gdzieś je przesyła?
Na comparetext.org diff działa w twojej przeglądarce. Dwa pliki JSON są porównywane przez JavaScript na twoim własnym komputerze, więc nic nie jest wysyłane na serwer, chyba że jawnie klikniesz Zapisz lub Udostępnij. To sprawia, że jest bezpieczne dla plików konfiguracyjnych, odpowiedzi API i innych danych, których nie chciałbyś wklejać na przypadkową stronę, która przesyła dane przy każdym naciśnięciu klawisza.
Dlaczego moje dwa pliki JSON pokazują każdą linię jako różną?
Prawie zawsze to formatowanie, nie prawdziwe zmiany. Jeden plik jest skrócony lub wcięty tabulatorami, drugi dwiema spacjami, albo klucze obiektów są w innej kolejności. Kliknij Formatuj po obu stronach, żeby używały tego samego wcięcia, a następnie posortuj klucze, żeby kolejność przestała mieć znaczenie. Po tym diff zwykle kurczy się do garści wartości, które naprawdę się zmieniły.
Jak porównać JSON ignorując kolejność kluczy?
Klucze obiektów JSON nie mają zdefiniowanej kolejności, więc {"a":1,"b":2} i {"b":2,"a":1} są równe. Żeby diff tekstowy się z tym zgodził, posortuj klucze po obu stronach przed porównaniem. W przeglądarce użyj opcji kanonizuj (sortuj klucze). W wierszu poleceń robi to jq: jq -S . file.json. Gdy oba pliki mają klucze w tej samej posortowanej kolejności, widoczne są tylko prawdziwe zmiany wartości.
Czy mogę porównywać duże pliki JSON bez zamrażania strony?
Tak, do pewnego stopnia. Diff liniowy pozostaje szybki na plikach z tysiącami linii, bo porównuje najpierw całe linie zamiast każdego znaku. Bardzo duże pliki (kilka megabajtów) lepiej obsługiwać narzędziem wiersza poleceń, jak jq lub git diff, które strumieniują dane. Dla wszystkiego, przez co możesz wygodnie przewijać w przeglądarce, diff online jest szybszą opcją.
Jaka jest różnica między diffem tekstowym a strukturalnym JSON?
Diff tekstowy porównuje pliki linia po linii, tak jak porównywałby dwa eseje. Diff strukturalny rozumie JSON, więc wie, że zmieniona kolejność klucza nie jest zmianą i że wartość przesunięta wewnątrz tablicy to przesunięcie, a nie usunięcie plus dodanie. Diffy tekstowe są szybsze i wystarczające dla większości przeglądów. Diffy strukturalne (np. JSON Patch według RFC 6902) mają znaczenie, gdy trzeba opisać zmianę jako dane, które program może zastosować.
Jak porównać dwie odpowiedzi API?
Zapisz każdą odpowiedź do pliku lub skopiuj ją z zakładki sieci w przeglądarce, następnie wklej starą odpowiedź po lewej, a nową po prawej. Sformatuj obie, żeby wcięcia się zgadzały, i posortuj klucze, jeśli API nie zwraca ich w stabilnej kolejności. Narzędzie api-response-diff jest dokładnie do tego dostrojone: wychwytywanie zmienionej nazwy pola, zmienionego kodu statusu lub wartości, która zmieniła się z ciągu na liczbę między dwoma wywołaniami.

Chcesz spróbować? Wklej swoje pliki do narzędzia do porównywania JSON i sprawdź, co się zmieniło.