Comparateur JSON

Comparez deux documents JSON et voyez ce qui a été ajouté, supprimé ou modifié. L’ordre des clés, l’indentation et la minification ne comptent pas.

Premier JSON
Second JSON
Résultat

Collez du JSON dans les deux volets pour les comparer

Comparer les données, pas le texte

Une comparaison ligne à ligne répond à une question sur l’écriture : quelles lignes de ce fichier ne sont pas des lignes de celui-là. C’est la bonne question pour de la prose et pour du code source, et la mauvaise pour du JSON, parce qu’un document JSON n’a pas une seule façon correcte d’être écrit. Réordonnez les membres d’un objet, changez l’indentation, minifiez un côté, et chaque ligne change alors que les données, elles, ne bougent pas du tout. Cet outil compare ce que les deux documents donnent une fois analysés, si bien que ce qu’il signale est ce qui a réellement changé. Voici un même document écrit deux fois : minifié sur la première ligne, puis mis en forme avec ses membres dans un autre ordre.

{"name":"checkout","port":8080,"tags":["a","b"]}

{
  "port": 8080,
  "tags": ["a", "b"],
  "name": "checkout"
}

Ces deux-là ne partagent pas une seule ligne, et cet outil ne signale aucune différence entre eux : les mêmes membres, les mêmes valeurs, le même ordre à l’intérieur de la liste. La conséquence pratique est que vous n’avez jamais à reformater un document pour qu’une comparaison fonctionne — ce qui reviendrait à modifier précisément la chose que vous vouliez comparer.

Ce qui ne compte jamais comme une différence

Quatre choses tenant à la façon dont un document est écrit sont ignorées, et elles le sont sans condition. Aucun réglage ne les réactive, parce qu’un outil qui le permettrait serait deux outils avec deux significations à expliquer, et le lecteur devrait savoir lequel des deux a produit la réponse qu’il a sous les yeux.

  • L’ordre dans lequel les membres d’un objet sont écrits. {"a": 1, "b": 2} et {"b": 2, "a": 1} portent les mêmes données, donc un sérialiseur qui émet ses clés dans un ordre différent à chaque exécution n’apparaît jamais ici comme un changement.
  • Les espaces sous toutes leurs formes — l’indentation, les sauts de ligne, l’espace après un deux-points. Un document minifié et un document mis en forme portant les mêmes données sont deux fois le même document.
  • La façon dont un nombre est écrit. 1e3 vaut 1000 et 1.50 vaut 1.5, car un nombre JSON a une valeur et pas d’orthographe : deux documents qui ne diffèrent que là portent les mêmes nombres, et rien n’est signalé à leur sujet.
  • La façon dont un caractère est échappé dans une chaîne. Les six caractères \u0041 et la lettre A sont la même chaîne d’un caractère.

Deux choses qui semblent avoir leur place dans cette liste en sont délibérément absentes. L’ordre des éléments d’une liste est une donnée — [1, 2] et [2, 1] sont deux documents différents — et un membre dont la valeur est null n’est pas la même chose qu’un membre absent, donc l’un face à l’autre est signalé plutôt que passé sous silence.

Ajouté, supprimé, modifié — et un changement de type

Chaque ligne du résultat relève exactement de l’une de trois catégories, et à elles trois elles couvrent tout ce que la comparaison peut trouver. Prenons ces deux documents :

{"host":"api.example.com","port":8080,"debug":true}

{"host":"api.example.com","port":"8080","retries":3}
  • Modifié en $['port'] : le membre est dans les deux documents et sa valeur n’est pas la même, donc les deux valeurs sont montrées. Celui-ci est en plus marqué comme un changement de type, parce que 8080 est un nombre et "8080" une chaîne.
  • Supprimé en $['debug'] : le membre est dans le premier document et pas dans le second.
  • Ajouté en $['retries'] : le membre est dans le second document et pas dans le premier.

Le membre nommé host est dans les deux avec la même valeur, il n’est donc pas signalé du tout. Un changement de type est une propriété d’une modification et non une quatrième catégorie de ligne : c’est la dérive qu’un schéma remarque et qu’une personne lisant deux fichiers ne remarque en général pas, alors elle est signalée là où elle se produit au lieu d’être laissée à votre œil.

Où se situe une différence, écrit comme un chemin

Chaque ligne nomme l’endroit où elle se situe, et elle le nomme comme un chemin dans le document plutôt que comme un numéro de ligne — un numéro de ligne serait un fait sur l’écriture, c’est-à-dire précisément ce que cet outil vient d’ignorer. Les chemins sont des chemins normalisés de la RFC 9535, la seule écriture que ce site emploie chaque fois qu’un emplacement dans du JSON doit être noté.

  • $ est le document entier, $['port'] le membre nommé port qui s’y trouve, et $['hosts'][1] l’élément d’indice 1 de la liste rangée sous hosts. Un membre s’écrit toujours entre crochets et guillemets simples, si bien qu’un nom contenant un point, une espace ou un crochet à lui n’exige rien de particulier.
  • Un chemin est une requête que vous pouvez exécuter. Collez-le dans le Testeur JSONPath de ce site avec le document auquel il appartient et il sélectionne exactement ce nœud, ce qui est le moyen le plus rapide de lire une différence dans son contexte.
  • C’est une requête contre le document où le nœud se trouve vraiment : le chemin d’un membre ajouté s’exécute sur le second document et celui d’un membre supprimé sur le premier. Rien ici ne nomme jamais un nœud absent.

Une ligne modifiée peut porter deux chemins, et c’est l’appariement de la section suivante qui transparaît : dès qu’une liste a gagné ou perdu un élément, le même élément se retrouve à un indice différent de chaque côté. Quand les deux chemins sont identiques — ce qui est presque toujours le cas — un seul est montré.

Comment deux listes sont appariées

Deux objets se comparent membre par membre, ce qui est simple parce qu’un membre a un nom. Deux listes n’ont pas de noms, seulement des positions, et les comparer position par position est ce qui rend illisible la plupart des sorties de comparaison : insérez un enregistrement en tête d’une longue liste et plus rien n’est là où il était, si bien qu’une comparaison qui lit la position comme une identité signale la liste entière comme différente — vrai au sens étroit et inutile à tous les autres. Les éléments sont donc appariés d’abord, d’après ce qu’ils sont et non d’après un champ que vous désigneriez, et seul ce que l’appariement laisse de côté est signalé.

{"hosts": ["alpha", "beta", "gamma"]}

{"hosts": ["alpha", "staging", "beta", "gamma"]}

Cela fait une seule ligne : staging a été ajouté en $['hosts'][1]. Les éléments nommés beta et gamma ont glissé d’un rang et ne sont pas mentionnés, parce que se déplacer n’est pas changer. C’est ce même appariement qui permet qu’un unique champ modifié à l’intérieur d’un élément de liste soit signalé comme une modification de ce champ, et non comme le remplacement de l’élément entier par un autre.

L’ordre compte toujours, et l’appariement ne prétend pas le contraire : [1, 2] face à [2, 1] revient comme un 1 supprimé en tête et un 1 ajouté après le 2, ce qui est bien ce qui s’est passé. Deux listes peuvent aussi être trop différentes pour que les apparier vaille son coût, et au-delà de ce point la comparaison repasse position par position et le dit en haut du résultat — une réponse bruyante est ainsi expliquée plutôt que mystérieuse.

Le rapport et l’arbre

La même comparaison est proposée sous deux formes, et passer de l’une à l’autre ne recalcule rien : l’arbre est dessiné à partir de la liste de différences même que le rapport énumère, si bien que les deux ne peuvent pas être en désaccord sur ce qui a changé ni sur la quantité.

  • Le rapport répond à la question de ce qui a changé : une ligne par différence, chacune portant son chemin, sa catégorie et sa valeur de chaque côté. C’est la vue à parcourir quand vous voulez la liste entière sous les yeux.
  • L’arbre répond à la question de l’endroit dans le document : les deux documents fondus en un seul plan, avec seulement les parties contenant une différence ouvertes. Chaque suite d’enfants consécutifs ne contenant rien se replie en une ligne disant combien elle représente — la paire de la section précédente se dessine en cinq lignes : le document, la liste qui s’y trouve, une ligne repliée valant pour un élément, l’élément ajouté, et une ligne repliée valant pour deux.
  • Le décompte à côté du titre du résultat, c’est toutes les différences qu’il y a. Quand la liste à l’écran est coupée par souci de longueur, ce décompte ne bouge pas, et une commande au pied de la liste montre le reste.
~! $['port'] 8080 -> "8080"
- $['debug'] true
+ $['retries'] 3

Voilà ce que produit la commande de copie pour les deux documents de la troisième section : un marqueur pour la catégorie, puis le chemin, puis les valeurs avec une flèche entre elles — un plus pour un membre ajouté, un moins pour un membre supprimé, un tilde pour une modification, et un tilde suivi d’un point d’exclamation quand le type a changé aussi. C’est écrit en symboles et non en mots à dessein, pour qu’un résultat collé soit le même texte quelle que soit la langue du site dans laquelle vous lisiez, et rien n’y est raccourci : une valeur que l’écran a coupée s’y trouve entière.

Ce que coûte l’analyse, et ce que l’outil dit tout haut

Comparer ce que deux documents donnent une fois analysés, c’est vivre avec ce que l’analyse coûte, et analyser du JSON n’est pas sans perte. Cela pèse ici plus que partout ailleurs sur ce site, parce que la réponse qu’un outil de comparaison peut le moins se permettre est « aucune différence » entre deux documents qui, eux, diffèrent bel et bien. Le texte de chaque volet est donc lu une seconde fois en tant que texte, et ce que l’analyse a laissé tomber est signalé à côté de la comparaison au lieu d’être avalé.

  • Un nom de membre écrit deux fois dans le même objet. Seule la copie la plus récente survit à l’analyse, donc seule cette copie a jamais été comparée et la première n’a été vue par personne.
  • Un nombre trop long pour survivre en tant que double. Deux littéraux différents peuvent s’analyser vers la même valeur, et les identifiants sont exactement l’endroit où cela arrive : 12345678901234567890 et 12345678901234567891 sont deux entiers différents et un seul nombre analysé, si bien que la comparaison seule déclarerait identiques deux documents dont les identifiants diffèrent.
{"user": {"id": 1}, "user": {"id": 2}}

Ce document s’analyse vers {"user": {"id": 2}}, et l’outil le dit sous le volet où il a été collé, avec la ligne et la colonne du premier cas et leur nombre total. Aucune des deux pertes n’arrête la comparaison ; toutes deux se placent à côté d’elle. Un document qui ne s’analyse pas du tout est signalé au même endroit, avec la position du problème chaque fois qu’il y en a une à donner, et les deux volets sont lus indépendamment — deux documents cassés produisent donc deux erreurs à la fois et non l’une après l’autre. Un document imbriqué bien plus profondément qu’une valeur ne peut être dessinée est refusé net, ce qui est un refus et non un onglet figé.

Pourquoi rien de ce que vous collez ne quitte cet onglet

Les deux documents restent dans votre navigateur. Il n’y a ni envoi, ni requête, ni serveur qui pourrait en garder une copie : la comparaison est de l’arithmétique qui tourne dans la page que vous avez déjà chargée, ce qui explique aussi qu’elle continue de fonctionner réseau coupé. C’est ce qui rend sûr d’y coller une réponse de production, la fiche d’un client ou une charge signée.

Rien n’est conservé non plus. Rechargez la page et les deux volets sont vides ; il n’y a pas d’historique, pas de compte et rien à effacer ensuite. La seule chose qui sorte jamais est ce que vous copiez vous-même.

Questions fréquentes

Est-ce que l’ordre des clés est ignoré ?
Oui, toujours, et c’est la raison d’être de l’outil. Deux objets portant les mêmes membres avec les mêmes valeurs sont identiques quelle que soit leur écriture, donc un sérialiseur qui émet ses clés dans un ordre différent à chaque exécution n’apparaît jamais comme un changement. L’indentation, la minification, la façon dont un nombre est écrit et celle dont un caractère est échappé sont ignorées pour la même raison, et rien de tout cela n’est un réglage que vous auriez pu basculer dans l’autre sens.
Un membre à null est-il la même chose qu’un membre absent ?
Non, et aucune option ne permet de les traiter pareillement. {"note": null} face à {} est signalé comme $['note'] supprimé. Un champ présent et vide est un autre énoncé qu’un champ que personne n’a envoyé — tout langage de schéma et tout langage à typage statique distingue les deux — donc les confondre ici jetterait quelque chose que les documents disent réellement.
[1, 2] et [2, 1] sont-ils signalés comme identiques ?
Non. Un tableau JSON est ordonné, donc deux listes portant les mêmes éléments dans un ordre différent sont deux documents différents, et les dire égaux serait faire dire à cet outil quelque chose de faux sur le format. Ce que vous obtenez, c’est une suppression et un ajout : le 1 a été supprimé en tête et rajouté après le 2. Les objets, eux, fonctionnent à l’inverse, parce que leurs membres sont nommés et non positionnés.
Que signifie le chemin sur chaque ligne ?
C’est l’endroit où la différence se situe dans le document, écrit comme un chemin normalisé de la RFC 9535 : $ est le document lui-même, $['port'] un de ses membres, $['hosts'][1] l’élément d’indice 1 de la liste rangée sous hosts. C’est aussi une requête que vous pouvez exécuter : collez-la dans le Testeur JSONPath de ce site avec le document auquel elle appartient et elle sélectionne exactement ce nœud, et rien d’autre.
Pourquoi une ligne a-t-elle montré deux chemins différents ?
Parce qu’apparier deux listes peut laisser le même élément à un indice différent de chaque côté. Comparez ["alpha", "beta"] avec ["intro", "alpha", "beta!"] et vous obtenez deux lignes : intro a été ajouté en $[0], et beta est devenu beta! — ce qui est $[1] dans le premier document et $[2] dans le second. Quand les deux chemins sont identiques, et ils le sont d’ordinaire, un seul est montré.
Que veut dire un changement de type sur une ligne ?
Que la valeur n’est pas seulement différente mais d’une autre nature : un nombre devenu une chaîne, une valeur devenue null, un objet devenu une liste. Un port écrit 8080 d’un côté et "8080" de l’autre est le cas le plus fréquent, et il signifie d’ordinaire qu’un sérialiseur ou un client a changé plutôt que les données. C’est marqué sur la ligne parce qu’une valeur modifiée et un contrat qui dérive appellent de votre part des réponses différentes.
Mes deux documents semblent identiques mais l’un écrit deux fois la même clé. Que se passe-t-il ?
L’analyse garde la copie la plus récente et laisse tomber la première, donc la comparaison voit un seul membre et peut très bien ne signaler aucune différence. L’outil lit le texte brut séparément exactement pour cela, et dit, sous ce volet, qu’un nom est écrit deux fois, combien de fois au total, et où se trouve le premier. Cette même seconde lecture attrape un entier trop long pour survivre en tant que double, qui est l’autre manière dont deux documents qui diffèrent peuvent être analysés en deux qui ne diffèrent plus.
Puis-je obtenir un correctif applicable ?
Non, et c’est une décision, pas un manque. Un JSON Patch s’applique une opération après l’autre, si bien que les indices qu’il contient se décalent à mesure qu’il s’exécute ; en émettre un est une promesse sur un programme que vous exécuteriez contre vos propres données, ce qui est une affirmation bien plus lourde que décrire ce qui diffère. Ce que vous pouvez emporter à la place, c’est le rapport en texte, depuis la commande de copie — le même dans toutes les langues du site, et raccourci nulle part.
Ce que je colle est-il envoyé à un serveur ?
Non. Les deux documents sont comparés dans votre navigateur et aucun ne le quitte : pas d’envoi, pas de requête, et rien de conservé d’une visite à l’autre. C’est ce qui rend sûr d’y coller une réponse de production, et c’est pourquoi l’outil fonctionne encore réseau coupé. Rechargez la page et les deux volets sont de nouveau vides.

Outils connexes

  • Comparateur de textes

    Quand le changement que vous voulez voir est dans l’écriture et non dans les données — une passe d’indentation, une clé déplacée, ou deux fichiers qui ne sont pas du JSON du tout — la comparaison qui le montre est celle ligne à ligne. C’est exactement ce que fait cette page-là, avec des options d’espaces et de casse et un correctif copiable.

  • Formateur JSON

    Valide et embellit le JSON, avec des emplacements d’erreur clairs.

  • Formateur SQL

    Formate et embellit le SQL — plusieurs dialectes.

  • Formateur XML

    Formate le XML et vérifie qu’il est bien formé — embellir ou minifier.