FAQ syntaxe et format : commentaires, virgules finales et précision
L’essentiel : Les commentaires sont-ils autorisés, pourquoi une virgule finale erre, les clés ont-elles besoin de guillemets, pourquoi 9007199254740993 change — douze conclusions, chacune vérifiée contre la RFC 8259.

L'essentiel : écrivez du JSON RFC 8259 simple — guillemets doubles, pas de commentaires, pas de virgules finales, UTF-8 sans BOM — pour tout ce qui franchit une frontière de processus ; utilisez JSONC ou JSON5 pour la configuration qui a besoin de commentaires et reconvertissez à l'exécution ; et sérialisez les entiers au-delà de 2^53-1 sous forme de chaînes côté serveur.
Ce que ces questions ont en commun : elles semblent « devoir fonctionner », mais la spécification est en réalité très stricte.
1. Qu'est-ce qui compte comme JSON valide
Quelle est la différence entre JSON, JSON5 et JSONC ?
Le JSON standard ne reconnaît que les chaînes entre guillemets doubles et n'autorise ni commentaires ni virgules finales. JSON5 et JSONC ajoutent tous deux des fonctionnalités par-dessus :
| Fonctionnalité | JSON (RFC 8259) | JSONC | JSON5 |
|---|---|---|---|
| Chaînes entre guillemets doubles | Pris en charge | Pris en charge | Pris en charge |
| Chaînes entre guillemets simples | Non pris en charge | Non pris en charge | Pris en charge |
| Commentaires | Non pris en charge | Pris en charge | Pris en charge |
| Virgules finales | Non pris en charge | Non pris en charge | Pris en charge |
| Clés sans guillemets | Non pris en charge | Non pris en charge | Pris en charge |
| Nombres hexadécimaux | Non pris en charge | Non pris en charge | Pris en charge |
NaN / Infinity |
Non pris en charge | Non pris en charge | Pris en charge |
| Extension de fichier | .json |
.jsonc |
.json5 |
Le principe de sélection est simple : utilisez toujours du JSON standard en franchissant des frontières de processus, tandis que les fichiers de configuration peuvent utiliser JSONC ou JSON5 selon ce que votre éditeur prend en charge.
tsconfig.json et .vscode/settings.json peuvent contenir des commentaires parce que l'éditeur les parse comme du JSONC ; les donner à un strict
JSON.parse erre encore.
JSON peut-il contenir des commentaires ?
Non. La grammaire de la RFC 8259 n'a aucune production de commentaire, donc tout // ou /* */ fait échouer le parsing.
Si vous voulez garder des explications dans la configuration, il y a trois alternatives :
{
"_comment": "以下超时单位是毫秒",
"timeout": 3000,
"retry": {
"$doc": "重试次数,0 表示不重试",
"count": 3
}
}
- Utilisez des champs conventionnels comme
_commentque le lecteur ignore simplement ; - Utilisez des champs préfixés
$pour réduire le risque de collision avec des champs métier ; - Passez simplement à JSONC ou YAML pour la configuration et convertissez en JSON standard à l'exécution.
Pourquoi une virgule finale cause-t-elle une erreur ?
Parce que dans la grammaire, tableaux et objets sont tous deux des structures « élément + séparateur », et aucune virgule n'est autorisée après le dernier élément.
{
"a": 1,
"b": 2,
}
Le fragment ci-dessus a une virgule en trop avant }, et l'erreur est généralement rapportée à la virgule ou à l'accolade fermante selon l'implémentation du parseur.
C'est aussi le piège le plus facile lors d'une migration depuis JavaScript — les littéraux d'objet JS autorisent les virgules finales, JSON non.
Les guillemets simples sont-ils autorisés ?
Non. Les chaînes doivent utiliser des guillemets doubles ; 'text' est une erreur de syntaxe.
Si les données ont été copiées depuis repr() de Python, un script shell ou une configuration écrite à la main, convertissez-les d'abord avec un outil
plutôt que d'espérer que le parseur soit indulgent.
Les clés peuvent-elles être sans guillemets ?
Non. Les noms de membres d'objet doivent être des chaînes entourées de guillemets doubles.
{ name: "json" }
C'est un littéral d'objet JavaScript, pas du JSON. Ajouter des guillemets le rend valide :
{ "name": "json" }
Le niveau supérieur peut-il être un tableau ou une chaîne ?
Oui. La RFC 8259 autorise toute valeur JSON au niveau supérieur : objets, tableaux, chaînes, nombres, true, false et null sont tous valides.
En pratique d'ingénierie, cependant, il est recommandé de n'utiliser que des objets ou des tableaux au niveau supérieur, pour deux raisons :
- Compatibilité : les parseurs plus anciens (de l'époque RFC 4627) exigent que le niveau supérieur soit un objet ou un tableau ;
- Sécurité : un tableau au niveau supérieur a historiquement porté un risque de détournement JSON. Les navigateurs modernes l'ont corrigé, mais les systèmes anciens supposent encore la convention.
Donc "hello" est du JSON valide et ["a", "b"] aussi, mais une API renvoyant {"data": [...]} est plus sûre.
2. Encodage et chaînes
Un BOM en début de fichier casse-t-il le parsing ?
Oui. La RFC 8259 exige explicitement que le texte JSON soit transmis en UTF-8 et ne doit pas ajouter de byte order mark (BOM).
Le BOM est les trois octets EF BB BF tout au début du fichier. Il est complètement invisible, mais il occupe la première position de la chaîne,
donc le parseur conclut que « le premier caractère n'est pas { » et erre — et la ligne et colonne rapportées sont généralement ligne 1, colonne 1, ce qui paraît déroutant.
Le « Enregistrer en UTF-8 » du Bloc-notes Windows inclut un BOM par défaut. Pour le vérifier et le supprimer :
# Check: output starting with efbbbf means a BOM is present
head -c 3 data.json | xxd
# Remove: skip the first 3 bytes with tail
tail -c +4 data.json > clean.json
Le JSON.parse du JavaScript moderne tolère un BOM (V8 le saute en interne), mais les implémentations strictes en Java, Go et Python non,
ce qui est une raison courante pour laquelle « le même fichier donne des résultats différents selon les langages ».
Les caractères chinois doivent-ils être convertis en \uXXXX ?
Non. La RFC 8259 exige que le texte JSON soit en UTF-8, donc le chinois peut apparaître directement ; {"name": "张三"} est parfaitement valide.
\uXXXX n'est qu'une orthographe alternative équivalente, utilisée pour les canaux qui ne peuvent pas représenter le caractère directement. La règle empirique :
- Corps de requête HTTP, fichiers, bases de données : écrivez le chinois directement, en économisant 3 octets par caractère ;
- En-têtes HTTP, paramètres d'URL, intégration dans une autre chaîne : utilisez des échappements pour éviter les problèmes d'encodage.
Les seuls caractères qui doivent être échappés sont les caractères de contrôle (U+0000 à U+001F). Par exemple, un retour à la ligne doit être écrit \n ;
vous ne pouvez pas mettre un retour à la ligne littéral dans une chaîne.
Les emoji peuvent-ils aller directement dans une chaîne ?
Oui, mais rappelez-vous qu'ils font deux unités de code UTF-16.
Les emoji et beaucoup de caractères rares (comme 𠮷) sont hors du BMP, donc en JavaScript "😀".length === 2.
Valider par nombre de caractères est donc facile à rater :
const text = "😀";
console.log(text.length); // 2, number of UTF-16 code units
console.log([...text].length); // 1, number of code points
console.log(Buffer.byteLength(text)); // 4, number of UTF-8 bytes
Si une limite de longueur de pseudo est calculée avec .length, un emoji consomme deux emplacements ; si le backend compte les octets, c'est quatre.
Lors de l'alignement des définitions, précisez clairement si vous parlez de points de code, d'unités de code ou d'octets.
3. Nombres et valeurs spéciales
Pourquoi le grand entier 9007199254740993 est-il devenu 9007199254740992 ?
Parce que le JSON.parse de JavaScript convertit les nombres en flottants double précision IEEE 754, et le plus grand entier sûr est 2^53-1.
Number.MAX_SAFE_INTEGER === 9007199254740991 // true
9007199254740993 // actually yields 9007199254740992
Tout nombre au-delà de 2^53-1 peut être arrondi dans ses derniers chiffres. Les victimes typiques sont les identifiants utilisateur de 19 chiffres générés par des algorithmes snowflake, les numéros de commande et les horodatages en nanosecondes.
Trois corrections, par ordre de préférence :
- Sérialisez l'identifiant comme chaîne côté serveur :
"id": "9007199254740993", supprimant le problème de précision à la racine ; - Utilisez une bibliothèque de parsing sans perte : des remplaçants de
JSON.parsepeuvent préserver les grands entiers tels quels, par exemplejson-bigint; - Extrayez avec une expression régulière pour l'affichage seulement : ne parsez pas tout le document, faites correspondre le champ cible comme texte.
Notez que JSON.stringify a le problème symétrique : un nombre hors de la plage entière sûre peut ne plus conserver sa valeur originale après sérialisation.
NaN et Infinity peuvent-ils aller dans du JSON ?
Non. La production de nombre JSON n'autorise que la notation décimale, et NaN, Infinity et -Infinity ne sont pas des valeurs JSON valides.
L'approche de JavaScript est la « dégradation silencieuse » :
JSON.stringify({ value: NaN }); // '{"value":null}'
JSON.stringify({ value: Infinity }); // '{"value":null}'
Pas d'erreur, NaN devient simplement null. C'est la scène classique où « le frontend a envoyé NaN et le backend a reçu null ».
Quand il faut transmettre cette sémantique, exprimez-la explicitement avec des chaînes ou des valeurs sentinelles convenues :
{ "ratio": null, "ratioState": "not-a-number" }
Les nombres peuvent-ils s'écrire 1e3 ou 0.5 ?
Oui. Les nombres JSON prennent en charge décimales et exposants, donc 1e3, 1E3, 0.5 et -0.5 sont tous valides.
Mais il y a des restrictions :
- Ils ne peuvent pas commencer ou finir par
.:.5et5.sont tous deux invalides, donc écrivez0.5et5; - Pas de zéros initiaux :
01est invalide ; - Pas d'hexadécimal :
0x1Fest invalide, c'est une extension JSON5 ; -0est valide, maisJSON.stringify(-0)produit0, perdant le signe.
4. Sémantique des objets
Que se passe-t-il avec des clés dupliquées ?
Le comportement est défini par l'implémentation ; la RFC 8259 dit seulement que les noms de clés « devraient » être uniques et ne spécifie pas ce qui se passe en cas de doublons.
En pratique la grande majorité des implémentations prennent la dernière valeur :
JSON.parse('{"a":1,"a":2}') // { a: 2 }
Mais vous ne pouvez pas vous y fier. Certaines bibliothèques Java, encoding/json de Go et json de Python diffèrent selon les versions :
certaines prennent la première valeur, certaines errent, d'autres prennent la dernière. Traiter les clés dupliquées comme une erreur est l'approche la plus sûre entre langages.
Vous ne pouvez pas trouver des clés dupliquées à l'œil — dans du JSON minifié la même clé peut être à des milliers de caractères. Utilisez un script :
# Extract keys that appear more than once at the same level, as a rough check
jq -r 'paths(scalars) | join(".")' data.json | sort | uniq -d
Que devient undefined ?
Cela dépend, et c'est l'endroit le plus sujet aux bugs :
JSON.stringify({ a: undefined }); // '{}' the key is dropped entirely
JSON.stringify({ a: undefined, b: 1 }); // '{"b":1}'
JSON.stringify([undefined]); // '[null]' becomes null inside arrays
JSON.stringify({ a: function () {} }); // '{}' functions are dropped too
Le même undefined signifie « le champ disparaît » dans un objet et « devient null » dans un tableau.
Si le backend prend des branches différentes pour « champ manquant » et « champ null », vous obtenez une incohérence ici.
Quand il faut exprimer « pas de valeur » explicitement, utilisez null plutôt que undefined.
Que se passe-t-il après le parsing de {"__proto__": {...}} ?
JSON.parse lui-même est sûr : __proto__ est traité comme une propriété propre ordinaire et ne change pas le prototype.
Ce qui est dangereux, c'est l'opération de fusion après le parsing :
const patch = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, patch); // some implementations pollute the prototype
La bonne approche est d'itérer les propriétés propres et de sauter __proto__, constructor et prototype.
Les détails sont dans la FAQ performance et sécurité.
5. Aide-mémoire
| Syntaxe | Valide | Remarques |
|---|---|---|
{"a": 1,} |
Non | Virgule finale |
{'a': 1} |
Non | Guillemets simples |
{a: 1} |
Non | Clé sans guillemets |
{"a": 1} // comment |
Non | Commentaires |
"hello" |
Oui | Une chaîne au niveau supérieur est valide, mais rarement utilisée dans les API |
{"a": 1e3} |
Oui | Les exposants sont pris en charge |
{"a": 01} |
Non | Zéro initial |
{"a": NaN} |
Non | Littéral invalide ; stringify le transforme en null |
\uFEFF{"a":1} |
Non | Un BOM ne fait pas partie du texte JSON |
{"a":1,"a":2} |
Contesté | Les implémentations prennent la dernière valeur, mais ne vous y fiez pas |