Compararea a două structuri JSON

compară două structuri JSON și listează adăugările, ștergerile și modificările, cheie cu cheie

De ce să compari două JSON-uri?

Compararea a două structuri JSON revine în mod regulat în viața unui dezvoltator. Un răspuns API care se schimbă după o actualizare. Un fișier de configurare care diverge între două medii. Un export de obiecte care trebuie să se alinieze pe o referință. Un diff JSON răspunde exact acestei întrebări: ce a fost adăugat, șters sau modificat, și unde?

Un diff linie cu linie (de tip git diff) adesea nu este suficient. Dacă formatarea diferă (spații, ordinea cheilor), diff-ul textual semnalează sute de linii deși structura datelor este identică. Un comparator JSON lucrează pe structură odată parsată, ceea ce elimină acest zgomot și relevă doar diferențele semantice.

Formatul diff-ului produs de instrument

Pentru fiecare diferență, instrumentul returnează:

  • un cale în format JSONPath simplificat, de exemplu $.user.address[0].city;
  • un tip printre added (adăugat la dreapta), removed (prezent la stânga doar), modified (valori diferite);
  • valoarea din stânga și/sau valoarea din dreapta în funcție de tip.

Un diff gol înseamnă că cele două JSON-uri sunt structural identice, independent de formatarea lor sau de ordinea cheilor.

Cum parcurge algoritmul cele două structuri

Algoritmul este recursiv. La fiecare nivel, identifică tipul celor două valori comparate:

  • Dacă ambele sunt obiecte asociative, ia uniunea cheilor. Pentru fiecare cheie, coboară recursiv, sau marchează added/removed dacă cheia nu există decât pe o parte.
  • Dacă ambele sunt tablouri ordonate, compară poziție cu poziție. O diferență la începutul tabloului poate decala tot restul, ceea ce produce un diff verbos: este o limită asumată a diff-ului structural naiv.
  • Dacă tipurile diferă (obiect față de tablou, scalar față de null), este marcat modified.
  • Dacă cele două valori sunt scalare (șir, număr, boolean, null), o simplă comparație strictă este suficientă.

Ordinea cheilor într-un obiect nu contează: {"a": 1, "b": 2} și {"b": 2, "a": 1} produc un diff gol. Este conform cu semantica JSON, unde ordinea nu este semnificativă. În schimb, ordinea elementelor unui tablou contează: un tablou este ordonat prin construcție.

Un exemplu concret

Iată două versiuni ale unui obiect utilizator:

// stânga
{
  "id": 42,
  "name": "Alice",
  "roles": ["admin", "editor"]
}

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

Diff-ul produs:

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

Cazuri de utilizare

  • Diff de medii: a compara ieșirea unui endpoint în pre-producție și producție. Foarte util în timpul unei migrări sau a unei reîmprospătări de cache.
  • Audit de migrare: a compara un export înainte și după transformare pentru a verifica că niciun câmp nu a fost pierdut.
  • Regresie API: înainte și după o modificare, a compara răspunsul pentru o cerere identică. Un diff gol confirmă non-regresia.
  • Sincronizare de configurări: a compara composer.json între două ramuri, două fișiere .eslintrc, două configurări Symfony.
  • Snapshot tests: a înlocui o comparație linie cu linie cu o comparație structurală într-o suită de teste de integrare.

Limitele diff-ului structural

Compararea tablourilor ordonate este o limită cunoscută a diff-urilor structurale naive. Dacă se inserează un element la începutul unui tablou, toate pozițiile următoare sunt decalate și diff-ul semnalează fiecare diferență ca o modificare. Pentru astfel de cazuri, algoritmi mai avansați există (Myers, Patience, diff prin cheie naturală), dar ies din cadrul unui instrument de comparație generalist.

Diff-ul nu spune nici de ce s-a întâmplat o schimbare: este o constatare. Pentru a analiza o regresie, trebuie încrucișată această constatare cu commit-urile, deploy-urile și parametrii cererii.

JSON diff vs JSON Patch (RFC 6902)

Un format complementar este JSON Patch (RFC 6902). Descrie, sub formă de operații (add, remove, replace, move, copy, test), cum să transformi un document JSON în altul. Acolo unde diff-ul nostru este o constatare (umană), JSON Patch este o rețetă (mașină). Cele două reprezentări sunt echivalente pentru cazurile simple, iar JSON Patch este util pentru API-uri RESTful care acceptă modificări parțiale.

Întrebări frecvente

Depinde diff-ul de ordinea cheilor?

Nu: pentru un obiect JSON, ordinea nu este semnificativă. Comparatorul produce același rezultat indiferent dacă cheile sunt sortate sau nu.

Cum să gestionez tablourile a căror ordine nu este importantă?

Instrumentul consideră implicit tablourile ca ordonate (este semantica JSON). Dacă tratezi mulțimi, sortează cele două tablouri după o cheie naturală înainte de a compara, sau utilizează un serviciu de comparație specializat care ia în considerare această semantică.

Care este diferența cu un diff Git?

Git compară linii de text. Dacă indentarea sau ordinea cheilor diferă, diff-ul Git este foarte verbos. Diff-ul JSON lucrează pe structura parsată și nu semnalează decât diferențele de date.

Este un JSON invalid acceptat?

Nu: dacă unul dintre cele două JSON-uri nu se parsează, instrumentul returnează o eroare. Validează mai întâi cu validatorul nostru JSON.

Exemplu de cerere

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

Schema de intrare

Câmp Tip Obligatoriu Implicit
left text
right text

Puncte de acces

  • GET https://cdrn.fr/api/v1/tools - listează toate instrumentele disponibile
  • GET https://cdrn.fr/api/v1/tools/json-diff - obține schema acestui instrument
  • POST https://cdrn.fr/api/v1/tools/json-diff/execute - execută acest instrument cu un payload JSON