Porównaj dwie struktury JSON

porównaj dwie struktury JSON i wypisz dodania, usunięcia i modyfikacje, klucz po kluczu

Dlaczego porównywać dwa JSON-y?

Porównywanie dwóch struktur JSON regularnie zdarza się w życiu programisty. Odpowiedź API, która zmienia się po aktualizacji. Plik konfiguracyjny rozbieżny między dwoma środowiskami. Eksport obiektów, który musi być zgodny z referencją. Diff JSON odpowiada dokładnie na to pytanie: co zostało dodane, usunięte lub zmodyfikowane i w którym miejscu?

Diff linia po linii (w stylu git diff) często nie wystarcza. Jeśli różni się formatowanie (spacje, kolejność kluczy), tekstowy diff sygnalizuje setki linii, podczas gdy struktura danych jest identyczna. Komparator JSON pracuje na strukturze po sparsowaniu, co eliminuje ten szum i ujawnia tylko semantyczne różnice.

Format diffu produkowanego przez narzędzie

Dla każdej różnicy narzędzie zwraca:

  • ścieżkę w uproszczonym formacie JSONPath, na przykład $.user.address[0].city;
  • typ spośród added (dodano po prawej), removed (obecne tylko po lewej), modified (różne wartości);
  • wartość po lewej i/lub wartość po prawej w zależności od typu.

Pusty diff oznacza, że oba JSON-y są strukturalnie identyczne, niezależnie od ich formatowania lub kolejności kluczy.

Jak algorytm przeszukuje dwie struktury

Algorytm jest rekurencyjny. Na każdym poziomie identyfikuje typ dwóch porównywanych wartości:

  • Jeśli oba są obiektami asocjacyjnymi, bierze sumę kluczy. Dla każdego klucza schodzi rekurencyjnie lub oznacza added/removed, jeśli klucz istnieje tylko po jednej stronie.
  • Jeśli oba są uporządkowanymi tablicami, porównuje pozycja po pozycji. Różnica na początku tablicy może przesunąć całą resztę, co produkuje gadatliwy diff: to znane ograniczenie naiwnego diffu strukturalnego.
  • Jeśli typy się różnią (obiekt vs tablica, skalar vs null), jest to oznaczone modified.
  • Jeśli obie wartości są skalarami (ciąg, liczba, boolean, null), wystarczy proste ścisłe porównanie.

Kolejność kluczy w obiekcie nie ma znaczenia: {"a": 1, "b": 2} i {"b": 2, "a": 1} produkują pusty diff. Jest to zgodne z semantyką JSON, gdzie kolejność nie jest znacząca. Natomiast kolejność elementów tablicy ma znaczenie: tablica jest z założenia uporządkowana.

Konkretny przykład

Oto dwie wersje obiektu użytkownika:

// gauche
{
  "id": 42,
  "name": "Alice",
  "roles": ["admin", "editor"]
}

// droite
{
  "id": 42,
  "name": "Alice Martin",
  "roles": ["admin", "viewer"],
  "active": true
}

Wyprodukowany diff:

  • $.name: modified, "Alice""Alice Martin"
  • $.roles[1]: modified, "editor""viewer"
  • $.active: added, true

Przypadki użycia

  • Diff środowisk: porównanie wyjścia endpointu w preprodukcji i w produkcji. Bardzo przydatne podczas migracji lub odświeżania cache.
  • Audyt migracji: porównanie eksportu przed i po transformacji, aby zweryfikować, że żadne pole nie zostało utracone.
  • Regresja API: przed i po modyfikacji porównanie odpowiedzi dla identycznego zapytania. Pusty diff potwierdza brak regresji.
  • Synchronizacja konfiguracji: porównanie composer.json między dwoma gałęziami, dwóch plików .eslintrc, dwóch konfiguracji Symfony.
  • Snapshot tests: zastąpienie porównania linia po linii porównaniem strukturalnym w zestawie testów integracyjnych.

Ograniczenia diffu strukturalnego

Porównywanie uporządkowanych tablic to znane ograniczenie naiwnych diffów strukturalnych. Wstawienie elementu na początku tablicy przesuwa wszystkie kolejne pozycje, a diff sygnalizuje każdą różnicę jako modyfikację. Dla takich przypadków istnieją bardziej zaawansowane algorytmy (Myers, Patience, diff według naturalnego klucza), ale wykraczają poza zakres uniwersalnego narzędzia porównawczego.

Diff nie mówi też, dlaczego doszło do zmiany: to obserwacja. Aby przeanalizować regresję, trzeba skrzyżować to z commitami, wdrożeniami i parametrami żądania.

JSON diff vs JSON Patch (RFC 6902)

Uzupełniającym formatem jest JSON Patch (RFC 6902). Opisuje on, w formie operacji (add, remove, replace, move, copy, test), jak przekształcić dokument JSON w inny. Tam gdzie nasz diff to obserwacja (ludzka), JSON Patch to przepis (maszynowy). Obie reprezentacje są równoważne dla prostych przypadków, a JSON Patch jest przydatny dla API RESTful, które akceptują częściowe modyfikacje.

Najczęściej zadawane pytania

Czy diff zależy od kolejności kluczy?

Nie: dla obiektu JSON kolejność nie jest znacząca. Komparator produkuje ten sam wynik, niezależnie od tego, czy klucze są posortowane czy nie.

Jak obsługiwać tablice, których kolejność nie ma znaczenia?

Narzędzie domyślnie traktuje tablice jako uporządkowane (taka jest semantyka JSON). Jeśli przetwarzasz zbiory, posortuj obie tablice według naturalnego klucza przed porównaniem lub użyj wyspecjalizowanej usługi porównawczej, która uwzględnia tę semantykę.

Jaka jest różnica w stosunku do diffu Git?

Git porównuje linie tekstu. Jeśli wcięcie lub kolejność kluczy różni się, diff Git jest bardzo gadatliwy. Diff JSON pracuje na sparsowanej strukturze i sygnalizuje tylko różnice danych.

Czy nieprawidłowy JSON jest akceptowany?

Nie: jeśli jeden z dwóch JSON-ów się nie parsuje, narzędzie zwraca błąd. Najpierw zweryfikuj go naszym walidatorem JSON.

Przykładowe zapytanie

curl -X POST https://cdrn.fr/api/v1/tools/json-diff/execute \
  -H "Content-Type: application/json" \
  -d '{"left":"...","right":"..."}'

Schemat wejściowy

Pole Typ Wymagane Domyślnie
left text
right text

Punkty końcowe

  • GET https://cdrn.fr/api/v1/tools - lista wszystkich dostępnych narzędzi
  • GET https://cdrn.fr/api/v1/tools/json-diff - zwraca schemat dla tego narzędzia
  • POST https://cdrn.fr/api/v1/tools/json-diff/execute - uruchamia to narzędzie z payloadem JSON