Jämför två JSON-strukturer

jämför två JSON-strukturer och listar tillägg, borttagningar och ändringar, nyckel för nyckel

Varför jämföra två JSON?

Att jämföra två JSON-strukturer återkommer regelbundet i en utvecklares vardag. Ett API-svar som förändras efter en uppdatering. En konfigurationsfil som divergerar mellan två miljöer. En objektexport som ska matcha en referens. En JSON-diff svarar precis på den frågan: vad har lagts till, tagits bort eller ändrats, och var?

En rad-för-rad diff (à la git diff) räcker ofta inte. Om formateringen skiljer sig (mellanslag, nyckelordning) signalerar text-diffen hundratals rader trots att datastrukturen är identisk. En JSON-komparator arbetar på strukturen efter parsning, vilket eliminerar det bruset och avslöjar endast semantiska avvikelser.

Formatet på diffen som verktyget producerar

För varje avvikelse returnerar verktyget:

  • en sökväg i förenklat JSONPath-format, till exempel $.user.address[0].city;
  • en typ bland added (tillagd till höger), removed (endast i vänster), modified (olika värden);
  • vänstervärdet och/eller högervärdet beroende på typ.

En tom diff betyder att de två JSON:erna är strukturellt identiska, oberoende av deras formatering eller nyckelordning.

Hur algoritmen traverserar de två strukturerna

Algoritmen är rekursiv. På varje nivå identifierar den typen på de två jämförda värdena:

  • Om båda är associativa objekt tar den unionen av nycklarna. För varje nyckel stiger den ned rekursivt eller markerar added/removed om nyckeln bara finns på ena sidan.
  • Om båda är ordnade arrayer jämför den position för position. En skillnad i början av arrayen kan förskjuta resten, vilket ger en utförlig diff: det är en erkänd begränsning hos naiv strukturell diff.
  • Om typerna skiljer sig (objekt mot array, skalär mot null) markeras det som modified.
  • Om båda värdena är skalärer (sträng, tal, boolean, null) räcker en enkel strikt jämförelse.

Nyckelordningen i ett objekt spelar ingen roll: {"a": 1, "b": 2} och {"b": 2, "a": 1} producerar en tom diff. Det är förenligt med JSON-semantiken, där ordningen inte är signifikant. Däremot spelar ordningen på elementen i en array roll: en array är ordnad till sin konstruktion.

Ett konkret exempel

Här är två versioner av ett användarobjekt:

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

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

Den producerade diffen:

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

Användningsfall

  • Miljödiff: jämföra utdata från en endpoint i förproduktion och produktion. Mycket användbart vid en migrering eller en cacheuppdatering.
  • Migrationsgranskning: jämföra en export före och efter transformation för att verifiera att inget fält gått förlorat.
  • API-regression: före och efter en ändring, jämföra svaret för en identisk förfrågan. En tom diff bekräftar icke-regression.
  • Konfigurationssynkronisering: jämföra composer.json mellan två branches, två .eslintrc-filer, två Symfony-konfigurationer.
  • Snapshot-tester: ersätta en rad-för-rad-jämförelse med en strukturell jämförelse i en integrationstestsvit.

Begränsningar i strukturell diff

Att jämföra ordnade arrayer är en känd begränsning hos naiv strukturell diff. Om man infogar ett element i början av en array förskjuts alla efterföljande positioner och diffen signalerar varje avvikelse som en modifiering. För sådana fall finns mer avancerade algoritmer (Myers, Patience, diff med naturlig nyckel), men de ligger utanför ramen för ett allmänt jämförelseverktyg.

Diffen säger inte heller varför en ändring skett: det är ett konstaterande. För att analysera en regression behöver man korsa konstaterandet med commits, driftsättningar och parametrar för förfrågan.

JSON diff vs JSON Patch (RFC 6902)

Ett kompletterande format är JSON Patch (RFC 6902). Det beskriver, i form av operationer (add, remove, replace, move, copy, test), hur man omvandlar ett JSON-dokument till ett annat. Där vår diff är ett konstaterande (mänskligt), är JSON Patch ett recept (för maskinen). De två representationerna är likvärdiga för enkla fall, och JSON Patch är användbart för RESTful-API:er som accepterar partiella ändringar.

Vanliga frågor

Beror diffen på nyckelordningen?

Nej: för ett JSON-objekt är ordningen inte signifikant. Komparatorn producerar samma resultat oavsett om nycklarna är sorterade eller inte.

Hur hanterar man arrayer där ordningen inte är viktig?

Verktyget betraktar som standard arrayer som ordnade (det är JSON-semantiken). Om du hanterar mängder, sortera båda arrayerna efter en naturlig nyckel före jämförelse, eller använd en specialiserad jämförelsetjänst som beaktar den semantiken.

Vad är skillnaden mot en Git-diff?

Git jämför textrader. Om indenteringen eller nyckelordningen skiljer sig är Git-diffen mycket utförlig. JSON-diff arbetar på den parsade strukturen och signalerar endast dataavvikelser.

Accepteras ogiltig JSON?

Nej: om en av de två JSON:erna inte kan parsas returnerar verktyget ett fel. Validera först med vår JSON-validator.

Exempelförfrågan

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

Indatasschema

Fält Typ Obligatorisk Standard
left text
right text

Slutpunkter

  • GET https://cdrn.fr/api/v1/tools - listar alla tillgängliga verktyg
  • GET https://cdrn.fr/api/v1/tools/json-diff - hämtar schemat för detta verktyg
  • POST https://cdrn.fr/api/v1/tools/json-diff/execute - kör detta verktyg med en JSON-payload