Les dix erreurs JSON qui cassent vraiment un payload
Classées par fréquence, pas par intérêt, avec le message d’erreur exact que chacune produit.
Chaque affirmation de cette page est soit mesurée, soit sourcée. Quand elle n’est ni l’une ni l’autre, la page le dit.
Presque tous les tickets « JSON invalide » se ramènent à dix choses. L’analyseur vous donne une position, parfois un caractère, et jamais la cause. Les voici dans l’ordre approximatif de leur fréquence, avec le message que vous aurez vu et la correction.
1. Virgules finales
De très loin la plus fréquente, parce que tous les autres formats que vous écrivez à longueur de journée les autorisent.
{ "a": 1, "b": 2, }
V8 vous renvoie un message qui ne mentionne ni les virgules ni ce que vous avez fait :
Expected double-quoted property name in JSON at position 18 (line 1 column 19)
L’analyseur a consommé la virgule, attendu une autre clé, et rencontré }. Faites maintenant la même chose dans un tableau :
Unexpected token ']', "[1,2,]" is not valid JSON
Formulation entièrement différente pour la même erreur, parce que dans l’analyseur le chemin des tableaux échoue sur une autre production de la grammaire. Si vous cherchez la chaîne d’erreur pour comprendre ce qui s’est passé, cette asymétrie explique pourquoi vous ne trouvez rien d’utile.
Python est plus direct, mais seulement depuis peu. Sur 3.13 et ultérieur :
Illegal trailing comma before end of object
Sur 3.12 et antérieur, la même entrée donne Expecting property name enclosed in double quotes: line 1 column 19 (char 18). Le même interpréteur, le même défaut dans votre fichier, deux explications différentes selon la version que votre CI se trouve épingler.
Correction : supprimez la virgule. { "a": 1, "b": 2 }.
2. Apostrophes
Un dictionnaire Python passé par print() ou str() au lieu de json.dumps() :
{'ok': True}
Ce n’est pas du JSON et ça ne l’a jamais été. L’échec survient sur la première apostrophe :
Expected property name or '}' in JSON at position 1 (line 1 column 2)
Remarquez aussi True, qui est un second échec, distinct, en attente derrière le premier. Les booléens JSON sont en minuscules.
{"ok": true}
Corrigez à la source : json.dumps(obj), et si la sortie part dans un fichier UTF-8 ou un corps HTTP, json.dumps(obj, ensure_ascii=False) pour que les caractères accentués restent lisibles au lieu de devenir des échappements \uXXXX. Ajoutez separators=(",", ":") si vous le voulez compact.
3. Clés sans guillemets
Un littéral d’objet JavaScript collé tel quel dans un champ JSON :
{ name: "ada", active: true }
Expected property name or '}' in JSON at position 2 (line 1 column 3)
JSON exige que chaque clé soit une chaîne entre guillemets doubles. Ni apostrophes, ni nue, ni un nombre. {"name": "ada", "active": true}. C’est le même genre de copier-coller que le précédent, et la correction est la même : sortez la valeur du runtime avec un vrai sérialiseur plutôt que d’un journal de console.
4. Caractères de contrôle non échappés
Un vrai saut de ligne à l’intérieur d’un littéral de chaîne :
{"note": "line one
line two"}
Bad control character in string literal in JSON at position 18 (line 1 column 19)
Python appelle ça Invalid control character at: line 1 column 19 (char 18). Dans les deux cas, l’analyseur vous dit qu’un caractère inférieur à U+0020 est apparu dans une chaîne, là où seul son échappement est autorisé.
{"note": "line one\nline two"}
Les tabulations posent le même problème et sont plus difficiles à voir, parce qu’une tabulation collée dans une valeur ressemble à des espaces. La cause profonde est presque toujours du JSON assemblé par concaténation de chaînes, où un champ contenant un saut de ligne est inséré tel quel. La dernière section traite cela comme il faut.
5. Chemins Windows
{"path": "C:\Users\ada\config.json"}
\U et \a ne sont pas des échappements valides. Python est explicite : Invalid \escape: line 1 column 13 (char 12). V8 dit Bad escaped character in JSON at position 13 (line 1 column 14), en pointant le U plutôt que la barre oblique inverse. Les neuf échappements légaux sont \" \\ \/ \b \f \n \r \t et \uXXXX. Tout le reste est une erreur, ce qui est la bonne conception et surprend en permanence.
{"path": "C:\\Users\\ada\\config.json"}
Les barres obliques normales fonctionnent très bien sous Windows dans presque toutes les API, et elles ne vous coûtent aucun doublement. Si vous avez un bloc de texte à insérer et que vous préférez ne pas le faire à la main, l’outil d’échappement s’en charge, et le déséchappement fait le trajet inverse.
6. Caractères invisibles
C’est celui qui vous mange un après-midi. Deux variantes :
Espace insécable (U+00A0). Copiez un extrait depuis une page de documentation, un client de messagerie ou un PDF, et les espaces entre les jetons ne sont peut-être pas des espaces. La RFC 8259 n’autorise exactement que quatre caractères blancs entre les jetons : espace, tabulation, retour chariot et saut de ligne. U+00A0 n’en fait pas partie, c’est donc une erreur de syntaxe, et il s’affiche exactement comme le caractère d’à côté.
Guillemets typographiques. Word et Google Docs corrigent automatiquement le guillemet droit U+0022 en la paire typographique U+201C et U+201D pendant que vous tapez. JSON n’accepte que U+0022. Un document qui paraît parfaitement guillemeté à l’écran ne contient aucun délimiteur de chaîne.
Aucune des deux variantes ne produit un message qui nomme le point de code. Selon l’endroit où tombe le caractère, vous obtenez Expected double-quoted property name in JSON at position 8, ou un Unexpected token ' ' qui vous réaffiche un caractère que vous ne distinguez pas d’un espace ordinaire. Collez le document dans le validateur : il nomme le caractère et son point de code au décalage exact, ce qui est le moyen le plus rapide de le trouver. Réparer du JSON les retire et vous dit ce qu’il a supprimé.
7. Commentaires
{
// le nom affiché de l’utilisateur
"name": "ada"
}
V8 signale Expected property name or '}' in JSON at position 4 (line 2 column 3). Python s’arrête sur Expecting property name enclosed in double quotes: line 2 column 3 (char 4). Les deux pointent la barre oblique, et aucun ne prononce le mot commentaire, si bien que le message se lit comme un problème de guillemets sur une ligne qui ne contient aucune chaîne.
JSON n’a pas de syntaxe de commentaire. Crockford l’a retirée délibérément, parce que les gens s’en servaient pour transporter des directives d’analyse. Si vous maîtrisez le consommateur, JSONC (ce que VS Code utilise pour ses propres réglages) autorise commentaires et virgules finales, et JSON5 autorise bien davantage. Sinon, déplacez la prose dans un champ, ou dans le schéma, là où les descriptions ont leur place. L’argumentaire complet vaut dix minutes si vous choisissez un format de configuration.
8. NaN et Infinity
{"ratio": NaN}
Unexpected token 'N', "{"ratio": NaN}" is not valid JSON
Le piège, c’est que Python l’émet par défaut. json.dumps({"ratio": float("nan")}) produit {"ratio": NaN} et ne lève rien, parce que l’encodeur de CPython est délibérément permissif et que son propre décodeur réaccepte la valeur. Tous les consommateurs non-Python la rejettent.
json.dumps(obj, allow_nan=False) # lève ValueError au lieu d’expédier du JSON invalide
Activez ça dès aujourd’hui dans votre couche de sérialisation. Un NaN qui atteint la production, c’est une division que vous n’avez pas protégée, et vous préférez la trouver à l’encodeur plutôt que dans l’analyseur d’un client.
9. Clés dupliquées
{"id": 1, "id": 2}
Aucune erreur. La RFC 8259 dit que les clés DEVRAIENT être uniques et laisse le comportement indéfini quand elles ne le sont pas. JavaScript et Python retiennent tous deux la dernière, donc ceci s’analyse en {"id": 2} et votre première valeur disparaît sans laisser de trace. D’autres analyseurs gardent la première, et certains lèvent une erreur. C’est le seul élément silencieux de la liste, ce qui en fait le pire. Passez un payload dans le validateur, qui signale les doublons au lieu de les écraser en silence.
10. Nombres
Deux échecs partagent cette place.
Zéros en tête. {"code": 007} est invalide. La grammaire JSON autorise un 0 seul, ou un chiffre de 1 à 9 suivi d’autres chiffres, et rien d’autre. Un code postal, un indicatif de pays ou une référence de pièce commençant par zéro est une chaîne. {"code": "007"}.
Entiers au-dessus de 2^53-1. {"id": 12345678901234567890} s’analyse sans problème et revient sous la forme d’un autre nombre, parce que JavaScript le stocke en double IEEE 754 et que Number.MAX_SAFE_INTEGER vaut 9007199254740991. Aucune erreur, aucun avertissement, mauvais enregistrement. Envoyez les grands identifiants en chaînes ; la version longue explique pourquoi toute autre correction est un contournement.
La correction structurelle
La moitié de cette liste (les points 2, 3, 4 et 5) vient de la même habitude : produire du JSON avec autre chose qu’un sérialiseur, en général une concaténation de chaînes ou un journal de console.
# chacun de ces cas est un bug qui attend la bonne entrée
body = '{"note": "' + note + '", "path": "' + path + '"}'
Un saut de ligne dans note le casse. Une barre oblique inverse dans path le casse. Un guillemet dans l’un ou l’autre le casse, et si cette entrée vient d’un utilisateur, c’est une injection, pas un problème de mise en forme.
body = json.dumps({"note": note, "path": path}, allow_nan=False)
Le sérialiseur échappe ce qui doit l’être, met des guillemets là où il en faut, et refuse ce qui ne peut pas être représenté. Ce n’est pas une préférence de style. Fabriquer du JSON à la main revient à réimplémenter correctement les règles d’échappement de la section 7 de la RFC 8259 dans chaque branche, et personne ne le fait.
Quand on vous remet un document cassé plutôt qu’un producteur cassé, Réparer du JSON applique les corrections ci-dessus et imprime la liste de chaque modification, pour que vous puissiez voir s’il a deviné quoi que ce soit avant de faire confiance au résultat.