JSON Toolkit

Le même JSON, quatre outils, quatre résultats : comment choisir un formateur

L’essentiel : Le formatage n'est pas sans perte. Avec un zéro initial, un BOM ou un entier à 20 chiffres, Python, Node, jq et json.tool ne s'accordent pas : certains lèvent une erreur, d'autres réécrivent silencieusement la valeur. Dix échantillons exécutés en local sur quatre chemins donnent un tableau de choix et un script de validation stricte.

Intermediate~6 min de lecturePublié le 2026-11-03Mis à jour le 2026-11-03
Quatre chemins d'analyse produisant des résultats différents pour le même JSON

Conclusion : la différence la plus grave entre formateurs JSON n'est pas le nombre d'espaces d'indentation, mais qu'une même entrée déclenche une erreur ici et réécrive silencieusement les données là. Un seul échantillon aux valeurs limites en apprend plus qu'une liste de fonctionnalités.

On croit le formatage « simple remise en forme ». En réalité, chaque chemin réinterprète le texte avec son propre analyseur. Quand l'analyseur est tolérant, l'erreur qui aurait dû être levée devient une modification silencieuse, tandis que la page affiche toujours un vert rassurant « Formatage réussi ».

1. Le constat : une entrée, quatre chemins, quatre résultats

Test réalisé avec Python 3.11.1, Node v22.13.1 et jq 1.7. Dix courtes chaînes JSON construites à la main ont été envoyées sur l'entrée standard, sans écriture disque ni changement de configuration. Les quatre chemins : json.loads, JSON.parse, jq -c ., python -m json.tool. ERR signifie exception et arrêt ; SILENT signifie aucune erreur mais une sortie différente de l'entrée.

Échantillon python json.loads node JSON.parse jq -c . python -m json.tool
S1 {"a":1,"b":[1,2]} OK OK OK OK
S2 {"a":1,} ERR ERR ERR ERR
S3 {'a':1} ERR ERR ERR ERR
S4 {"a":01} ERR char 6 ERR position 6 SILENT {"a":1} ERR
S5 {"a":1 /*x*/} ERR char 7 ERR ERR ERR
S6 \ufeff{"a":1} ERR utf-8-sig ERR position 0 SILENT {"a":1} ERR
S7 {"id":9007199254740993} OK fidèle SILENT 9007199254740992 OK fidèle OK
S8 {"a":1,"a":2} SILENT {"a":2} SILENT {"a":2} SILENT {"a":2} OK
S9 {"a":1 ERR char 6 ERR ERR EOF ERR
S10 <html>502</html> ERR char 0 ERR Unexpected token '<' ERR ERR

Quatre échantillons sur dix ont produit « pas d'erreur, mais données modifiées » : S4, S6, S7, S8. Si l'on ne croit que le voyant vert, on a quatre chances sur dix de tenir autre chose que les données d'origine. Le plus piégeux : aucun de ces cas ne figure dans une liste d'erreurs de syntaxe. Le fichier s'ouvre, se copie, s'exporte ; la faute est dans le contenu, pas dans la syntaxe.

2. La cause : syntaxe et sémantique sont deux couches distinctes

La RFC 8259 est explicite. La section 1.2 recommande d'éviter, pour une interopérabilité maximale, des pratiques qu'ECMA-404 autorise ; la section 4 dit que les noms d'objets SHOULD être uniques sans prescrire le traitement des clés dupliquées ; la section 6 ne donne sur les nombres que des conseils d'interopérabilité. ECMA-404 va plus loin et se déclare « agnostique quant à la sémantique des nombres » : seule la représentation est spécifiée, pas le stockage dans le langage. La norme laisse donc volontairement une large zone frontière aux implémenteurs.

Le « formatage » se scinde alors en deux couches : la couche syntaxique décide si le texte est légal, la couche sémantique décide la valeur qu'il devient. La première a une réponse standard, la seconde non. jq lisant 01 comme 1 et sautant un BOM initial est un choix sémantique ; Node poussant un entier de 20 chiffres dans un double IEEE 754 est un choix du runtime ; les quatre chemins gardant la dernière valeur de {"a":1,"a":2} est une convention partagée des analyseurs courants. Rien de tout cela ne viole « analysable », mais tout modifie les données.

Corriger les erreurs d'analyse JSON travaille sur la couche syntaxique et traduit le message d'erreur en coordonnées. Cet article traite de l'étape d'avant : si l'outil ne lève aucune erreur, on n'obtient même pas de coordonnées.

3. Quatre réécritures silencieuses, une par une

Isolés, les quatre échantillons montrent des changements si petits qu'un rapide coup d'œil les manque :

{"a": 01}                          // zéro initial : jq sort {"a":1}
\ufeff{"a": 1}                     // BOM : jq saute les trois premiers octets
{"id": 9007199254740993}           // entier 20 chiffres : JSON.parse sort ...992
{"a": 1, "a": 2}                   // clé dupliquée : les quatre gardent {"a":2}

Zéro initial et BOM relèvent du type « l'entrée était déjà invalide, mais l'outil a été tolérant ». 01 est illégal en JSON standard (RFC 8259 section 6), un BOM ne doit pas ouvrir un texte JSON ; Python et Node rejettent, jq continue. Pratique pour nettoyer des logs, catastrophique en validation de contrat : l'amont pense avoir envoyé des données invalides à corriger, l'aval détient déjà un résultat « apparemment normal ».

Le grand entier est le type « entrée parfaitement légale, mais le runtime ne peut pas la contenir ». 9007199254740993 est un nombre JSON légal, mais au-delà de 2^53-1, JSON.parse ne peut représenter que 9007199254740992. Le int de Python et jq conservent la valeur. S'il s'agit d'un numéro de commande ou d'un Snowflake ID, un dernier chiffre changé suffit à désaccorder la comptabilité.

Les clés dupliquées sont le type « la norme ne dit rien, chaque implémentation choisit ». Les quatre chemins prennent silencieusement la dernière valeur, sans avertissement. La RFC 8259 dit seulement SHOULD : ce n'est pas un bug, mais c'est la classe de problème la plus difficile à tracer lors de concaténations de config ou de fusions de correctifs.

4. Rapport d'erreur : qui donne une position, qui donne une ligne

Les six échantillons en échec diffèrent aussi beaucoup par l'information fournie. La JSONDecodeError de Python expose msg, lineno, colno et pos ; Node v22 rapporte position N (line 1 column M) avec un extrait du texte fautif ; python -m json.tool réutilise les informations de Python ; jq ne donne que parse error: ... at line N, sans offset de caractère.

Sur du JSON minifié d'une seule ligne, numéros de ligne et de colonne sont presque inutiles — tout est en ligne 1. Ce qu'il faut, c'est un offset de caractère. Le script ci-dessous normalise la validation en « erreur + offset + contexte » :

import json, sys

def strict_check(text: str) -> None:
    try:
        json.loads(text)
        print("OK")
    except json.JSONDecodeError as e:
        print(f"ERR {e.msg} | char {e.pos} | line {e.lineno} col {e.colno}")
        lo, hi = max(0, e.pos - 30), min(len(text), e.pos + 30)
        print("...", text[lo:hi].replace("\n", "\\n"), "...")
        sys.exit(1)

strict_check(open(sys.argv[1], encoding="utf-8").read())

Il est plus strict que jq . (zéro initial et BOM rejetés) et fournit e.pos. Comment convertir line 1 column 23611 en offset d'octet, et pourquoi offsets de caractères et d'octets diffèrent d'un facteur trois dans les documents CJK, est traité dans Déboguer du JSON minifié ; ce n'est pas répété ici.

5. La correction : séparer « validation stricte » et « mise en forme »

Conclusion simple : un seul bouton « Formater » ne doit pas porter à la fois validation et embellissement. D'abord un analyseur strict pour la légalité, ensuite le choix de la présentation. Une ligne suffit à isoler la première étape :

python3 -m json.tool input.json > /dev/null && echo "syntaxe OK" || echo "syntaxe invalide"
python3 -c "import json,sys; d=json.load(open(sys.argv[1],encoding='utf-8')); print(type(d).__name__, len(d))" input.json

La première ligne répond seulement à « analysable ? », la seconde vérifie « la structure est-elle celle attendue ? ». Avec de grands entiers, lire avec Python ou jq plutôt que JSON.parse ; avec un BOM possible, ouvrir explicitement en encoding='utf-8-sig' au lieu d'espérer un saut automatique ; avec des clés possiblement dupliquées, balayer séparément avant l'analyse, car aucun des quatre chemins ne prévient. Écrit en CI, c'est exactement le pipeline de Chaîne d'outils éditeurs, ligne de commande et automatisation, avec une couche supplémentaire d'assertions de fidélité des données.

6. Choisir : tableau comparatif des quatre chemins

Chemin Force Risque Usage conseillé
python json.loads / json.tool Strict, donne ligne/colonne et offset, entiers sans perte de précision Refuse zéro initial et BOM, encodage à traiter explicitement Validation de contrat, CI, tri de logs
node JSON.parse Natif dans le navigateur, erreur avec extrait de source Grands entiers tronqués silencieusement, erreur sur BOM Débogage front, outils in-browser
jq -c . Adapté aux pipes, résultat le plus rapide Zéro initial et BOM passent en silence, pas d'offset Pipelines d'exploitation, extraction rapide
Outils navigateur /parse, /editor Vue arborescente, tri des clés, copie de chemin, sans installation Comportement dépend de l'analyseur de la page, à vérifier une fois Inspection manuelle, dépannage ponctuel

La question n'est pas « qui a le plus de fonctions », mais « va-t-il mentir sur mes données ? ». Pour des identifiants de commande, des montants ou des ID d'appareil, préférer Python ou un outil déclarant explicitement sa fidélité ; pour un simple débogage de structure jamais persisté, le navigateur suffit. Le /parse de ce site valide et formate dans le navigateur — idéal pour « voir à quoi ça ressemble », mais pour les grands entiers, revérifier avec le script ci-dessus.

7. La confidentialité n'est qu'une des frontières

« Mes données seront-elles envoyées ? » est la question la plus posée sur les outils en ligne, mais elle est indépendante de « mes données seront-elles modifiées ? ». Un outil local fonctionnant hors ligne peut quand même tronquer un entier de 20 chiffres ; inversement, un outil en ligne qui signale strictement les erreurs peut POSTer le texte original vers un serveur. Les deux propriétés se vérifient séparément, l'une ne remplace pas l'autre. La FAQ confidentialité donne trois vérifications en deux minutes ; cet article apporte l'autre moitié : avec la liste d'échantillons ci-dessous, confirmer que l'outil ne réécrit aucune valeur en silence.

8. Une liste d'autocontrôle copiable

Enregistrer S1–S10 dans cases.txt, une ligne par échantillon, les coller dans chaque outil candidat et vérifier trois points : les échantillons invalides échouent-ils vraiment, l'échec donne-t-il une position concrète, la sortie d'un échantillon valide est-elle identique caractère par caractère à l'entrée ? Surveiller surtout S4, S6, S7, S8 — une seule ligne SILENT signifie que ce chemin ne peut pas servir seul à la validation de données.

Ajouter trois contraintes d'ingénierie : placer la validation stricte dans un hook pre-commit plutôt que de découvrir un entier discordant en production ; écrire les fichiers en UTF-8 sans BOM dans tous les langages ; pour le JSON de tiers, conserver le corps de réponse brut et n'utiliser le résultat formaté que pour la lecture, jamais pour la réécriture.

Questions fréquentes

Un formatage réussi signifie-t-il que le JSON est sain ?

Non. S4, S6, S7 et S8 ont tous « réussi » le formatage, mais zéro initial, BOM, entier de 20 chiffres et clé dupliquée ont été réécrits ou avalés. Le formatage prouve seulement qu'un analyseur a accepté le texte, pas que les valeurs égalent celles envoyées. Assertionner les champs critiques valeur par valeur.

Pourquoi jq accepte-t-il zéro initial et BOM que Python rejette ?

jq possède son propre analyseur lexical : il est tolérant sur des nombres comme 01 et saute un BOM initial, tandis que Python et Node suivent strictement la RFC 8259. Ce n'est pas un bug de version, mais le compromis entre analyseur tolérant et analyseur strict. Le premier fait gagner du temps en nettoyage, le second est obligatoire en validation de contrat.

Comment éviter qu'un grand entier devienne 9007199254740992 ?

Le Number de JavaScript est un double IEEE 754 dont la limite sûre est 2^53-1. Lire un ID de 20 chiffres avec JSON.parse le tronque nécessairement. Stocker ces ID en chaîne, ou utiliser le int de Python, jq, ou un analyseur compatible BigInt. Si le front a vraiment besoin d'un nombre, remplacer ce champ par une chaîne avant l'analyse.

Outil en ligne ou ligne de commande locale, lequel choisir ?

Selon la sensibilité et l'usage : pour inspecter une structure ou trouver un champ, le navigateur est le plus rapide ; pour une pipeline avec assertions de fidélité, python -m json.tool plus un vérificateur strict. Dans les deux cas, lancer d'abord S1–S10, confirmer erreurs et sorties, puis décider quelle classe de données on accepte d'y injecter.