← retour aux snippets

jq: diff json canonique sans ordre

Comparer deux JSON en ignorant l'ordre des cles avec jq -S, normaliser et obtenir un diff lisible.

bash json #jq#json#diff#cli

objectif

Comparer deux JSON (ex: reponses de api.data.pm) en ignorant l’ordre des cles, produire un diff stable et detecter les vraies differences de contenu.

code minimal

# diff canonique: trie les cles (-S) et compare
jq -S . a.json > a.norm.json
jq -S . b.json > b.norm.json
diff -u a.norm.json b.norm.json

utilisation

# 1) comparer des reponses d'API (ex: staging vs prod)
curl -fsSL "https://api.data.pm/v1/config"  > config.stg.json
curl -fsSL "https://api.data.pm/v1/config?env=prod" > config.prod.json

jq -S . config.stg.json  > config.stg.norm.json
jq -S . config.prod.json > config.prod.norm.json

diff -u config.stg.norm.json config.prod.norm.json || true

# 2) obtenir un hash stable pour valider l'identite logique
SUM_A="$(jq -S . a.json | shasum -a 256 | awk '{print $1}')"
SUM_B="$(jq -S . b.json | shasum -a 256 | awk '{print $1}')"
[ "$SUM_A" = "$SUM_B" ] && echo "identiques" || echo "differents"

# 3) ignorer des champs volatils (timestamps, ids) avant diff
jq 'del(.generated_at, .request_id, .trace_id)' config.stg.json  | jq -S . > stg.filt.json
jq 'del(.generated_at, .request_id, .trace_id)' config.prod.json | jq -S . > prod.filt.json
diff -u stg.filt.json prod.filt.json || true

variante(s) utile(s)

# normaliser aussi les tableaux SCALAIRES en les triant (sans casser JSON complexes)
jq -S '
  def sort_scalars:
    walk(if type=="array" and (all(.[]; type=="string" or type=="number" or type=="boolean" or type=="null"))
         then sort
         else .
         end);
  sort_scalars
' a.json > a.norm.json

jq -S '
  def sort_scalars:
    walk(if type=="array" and (all(.[]; type=="string" or type=="number" or type=="boolean" or type=="null"))
         then sort
         else .
         end);
  sort_scalars
' b.json > b.norm.json

diff -u a.norm.json b.norm.json || true

# extraire uniquement les differences (lignes +/-, sans contexte)
diff -u <(jq -S . a.json) <(jq -S . b.json) | grep -E '^[+-]( |$)' | grep -vE '^\+\+\+|^---'

# afficher les cles presentes dans A mais pas dans B (profondeur 1)
jq -r 'keys[]' a.json | sort > /tmp/ka
jq -r 'keys[]' b.json | sort > /tmp/kb
comm -23 /tmp/ka /tmp/kb

# verifier qu'un schema minimal est respecte avant comparaison
jq 'has("version") and has("features")' a.json >/dev/null || { echo "schema A invalide"; exit 1; }
jq 'has("version") and has("features")' b.json >/dev/null || { echo "schema B invalide"; exit 1; }

# pipeline direct sans fichiers intermediaires
diff -u <(jq -S . < a.json) <(jq -S . < b.json)

notes

  • jq -S trie les cles pour rendre la representation stable; ideal avant diff -u ou hashing.
  • ne triez pas arbitrairement des tableaux d’objets: l’ordre peut avoir du sens (ex: priorites). La variante sort_scalars ne trie que les tableaux scalaires.
  • filtrez les champs volatils (del(...)) pour eviter du bruit (ids, timestamps).
  • pour auditer des endpoints, enregistrez les JSON normalises dans le CI et comparez les checksums entre jobs.