JSON Toolkit

Lire les frontières de la grammaire JSON depuis la RFC 8259

L’essentiel : Traduire les productions grammaticales de la RFC 8259 en langage courant : quels caractères peuvent apparaître directement, lesquels doivent être échappés, et où la spécification laisse volontairement de la latitude.

Débutant~11 min de lecturePublié le 2026-10-30Mis à jour le 2026-10-30
Illustration de l'article Lire les frontières de la grammaire JSON depuis la RFC 8259

L'essentiel : quand une erreur de syntaxe JSON apparaît, trouvez le premier caractère illégal et classez-le comme guillemet simple, virgule finale, commentaire ou caractère de contrôle au lieu de relire tout le document ; côté encodage, confirmez UTF-8 sans BOM et transportez les entiers au-delà de 2^53 - 1 sous forme de chaînes.

Le corps de la spécification ne fait qu'une douzaine de pages, et pourtant derrière chaque « DOIT », « DEVRAIT » et « PEUT » se cache un incident qui a un jour empêché une équipe de dormir.

1. Comment les trois textes s'articulent

Apprendre JSON devient souvent confus à cause de trois numéros : RFC 4627, RFC 7159, RFC 8259, plus ECMA-404. La relation est en réalité assez claire :

Texte Date Statut Différence clé
RFC 4627 2006 Remplacée Le niveau supérieur doit être un objet ou un tableau
RFC 7159 2014 Remplacée Niveau supérieur assoupli à toute valeur JSON
RFC 8259 2017.12 Norme actuelle UTF-8 explicite, conseils d'interopérabilité ajoutés
ECMA-404 2013, deuxième édition 2017.12 Spécification de grammaire Définit seulement la grammaire, ne couvre ni encodage ni transport

Laquelle lire ? La RFC 8259 seule suffit. ECMA-404 est équivalente pour sa partie grammaticale ; la différence est que la RFC 8259 spécifie aussi « transport en UTF-8 » et « les problèmes d'interopérabilité dont les implémenteurs doivent se méfier ».

2. Six types de valeurs

Les productions grammaticales de la spécification peuvent se condenser en un seul schéma :

JSON-text  = ws value ws
value      = object / array / number / string / "true" / "false" / "null"
object     = "{" [ member *( "," member ) ] "}"
member     = string ws ":" ws value
array      = "[" [ value *( "," value ) ] "]"

Il n'y a que six points clés :

  1. Les objets utilisent des accolades, les membres sont séparés par des virgules, et une virgule après le dernier membre est interdite ;
  2. Les tableaux utilisent des crochets, les éléments sont séparés par des virgules, et les virgules finales sont également interdites ;
  3. Les nombres n'autorisent aucun + initial, aucun zéro initial (01 est invalide), aucun point décimal nu (1. est invalide), ni NaN ni Infinity ;
  4. Les chaînes doivent utiliser des guillemets doubles ; les guillemets simples sont invalides ;
  5. Les booléens et null sont des littéraux en minuscules ; True et NULL sont tous deux invalides ;
  6. Seuls les caractères d'espacement 0x20 (espace), 0x09 (tabulation), 0x0A (retour à la ligne) et 0x0D (retour chariot) peuvent apparaître entre les éléments structurels.

L'extrait ci-dessous est du JSON valide, bien que beaucoup le croient faux la première fois qu'ils le voient — parce que c'est une chaîne nue au niveau supérieur :

"just a string"

Depuis la RFC 7159, le niveau supérieur peut effectivement être n'importe quelle valeur. Dans les API, cependant, il est encore recommandé de n'utiliser que des objets ou des tableaux ; la raison est en section 6.

3. Ce qui peut réellement apparaître directement dans une chaîne

C'est la partie où « ça marche sur ma machine » trompe le plus facilement. Les règles de la spécification pour les chaînes sont :

  • Caractères autorisés à apparaître directement : %x20-21, %x23-5B, %x5D-10FFFF ;
  • Autrement dit, le guillemet double " (0x22) et la barre oblique inverse \ (0x5C) doivent être échappés ;
  • Tous les caractères de contrôle sous 0x20 doivent être échappés, y compris le retour à la ligne, la tabulation et le retour chariot ;
  • Les seules séquences d'échappement disponibles sont \", \\, \/, \b, \f, \n, \r, \t et \uXXXX.
{
  "ok": "Chinese text, emoji 🎉 and full-width symbols can be written directly",
  "escaped": "A line break goes here\nand a tab\tfinishes it",
  "control": "\u0007 is the bell character and must be escaped",
  "quote": "He said: \"don't use single quotes\""
}

\/ est un échappement hérité (il empêche </script> de fermer trop tôt). Les scénarios modernes en ont rarement besoin, mais les parseurs doivent le prendre en charge.

4. Encodage : UTF-8, sans BOM

La section 8.1 de la RFC 8259 dit trois choses :

  1. Le texte JSON utilisé pour l'échange doit être encodé en UTF-8 ;
  2. Les implémentations ne doivent pas ajouter de BOM (EF BB BF) au début du texte ;
  3. Les parseurs peuvent ignorer le BOM pour être plus robustes.

Deux échecs typiques dans le monde réel :

  • Le Bloc-notes Windows enregistre en « UTF-8 avec BOM », le fichier est rejeté par JSON.parse avec Unexpected token ;
  • Un fichier JSON chinois enregistré en GBK devient du texte brouillé sur un pipeline qui décode seulement en UTF-8, et pourtant la syntaxe passe par hasard, donc l'erreur est reportée à la couche métier.

Le traitement est simple : retirez le BOM à la lecture du fichier, puis décodez explicitement en UTF-8 ; à l'écriture, assurez-vous qu'aucun BOM n'est ajouté.

/**
 * Removes the UTF-8 BOM at the start of a string.
 * @param text The original text
 * @returns The text without a BOM
 */
function stripBom(text: string): string {
  return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
}

Une note sur les types de média pendant qu'on y est : application/json n'accepte plus le paramètre charset ; en écrire un ne lève pas d'erreur, mais c'est une pratique obsolète.

5. Trois endroits où la spécification laisse volontairement de la place

Les endroits où la spécification refuse de trancher sont exactement ceux d'où viennent les différences d'implémentation.

1. Plage et précision des nombres

La RFC 8259 dit seulement « cette spécification ne limite ni la plage ni la précision des nombres », tout en notant que la plupart des implémentations utilisent le double précision IEEE 754. Cela signifie que le même 9223372036854775807 devient 9223372036854776000 en JavaScript, mais reste exact dans un long Java.

Conclusion : les entiers au-delà de 2^53 - 1, et les montants monétaires qui exigent beaucoup de décimales, devraient toujours être transportés sous forme de chaînes, avec le pattern écrit explicitement dans le schéma.

2. Clés dupliquées

La formulation de la spécification est « les noms au sein d'un objet devraient (SHOULD) être uniques » ; elle ne dit pas quoi faire quand ils ne le sont pas.

  • JSON.parse garde le dernier ;
  • Certains parseurs gardent le premier ;
  • Certaines bibliothèques lèvent simplement une erreur.

Conclusion : n'écrivez pas de clés dupliquées, et ne comptez sur aucun comportement particulier dans vos validateurs. Ajouter une étape de « détection de clés dupliquées » au build coûte bien moins cher que d'enquêter ensuite.

3. Limites de profondeur et de longueur

La spécification n'impose aucune limite de profondeur d'imbrication, mais chaque implémentation en a une. V8 lève RangeError: Maximum call stack size exceeded à des dizaines de milliers de niveaux d'imbrication. Les serveurs doivent imposer leurs propres limites ; c'est une exigence de sécurité, pas une exigence de spécification.

6. Tester sa compréhension avec des contre-exemples

Chaque ligne ci-dessous est du JSON invalide. Donnez-les à un parseur et voyez si les messages d'erreur sont utiles :

{ 'name': 'json' }        // single quotes
{ name: "json" }          // key name not quoted
{"a": 1,}                 // trailing comma
[1, 2, 3,]                // trailing comma in an array
{"a": 01}                 // leading zero
{"a": +1}                 // leading plus sign
{"a": .5}                 // missing integer part
{"a": NaN}                // non-standard literal
{"a": undefined}          // not a JSON value
{"a": 1} // comment       // comment

Un cas limite intéressant : {"a": 1} suivi d'un commentaire est valide en JSON5, valide en JSONC, et invalide en JSON standard. Donc « le fichier de configuration fonctionne mais l'API le rejette » est presque toujours un problème de dialecte.

7. Liste de vérification de production

  • [ ] Tout JSON transmis à l'extérieur utilise application/json sans charset ;
  • [ ] Les pipelines de lecture/écriture de fichiers retirent le BOM et utilisent un UTF-8 explicite ;
  • [ ] Les entiers au-delà de 2^53 sont transportés sous forme de chaînes et annotés dans le schéma ;
  • [ ] Les clés dupliquées sont détectées au build ;
  • [ ] Le serveur limite la taille du corps de requête et la profondeur d'imbrication ;
  • [ ] Les valeurs de niveau supérieur des API publiques sont des objets ou des tableaux, jamais des scalaires nus.

FAQ

Comment savoir rapidement si un texte est du JSON valide ?

Donnez le texte à un parseur ou à un outil de parsing en ligne, lisez la position du premier caractère illégal, puis vérifiez-la contre les six types de valeurs et les règles d'espacement de la RFC 8259. Le niveau supérieur peut être n'importe quelle valeur JSON, mais entre les éléments structurels seuls l'espace, la tabulation, le retour à la ligne et le retour chariot sont autorisés. Un guillemet simple, une clé sans guillemets, une virgule finale ou un commentaire signifient que ce n'est pas du JSON standard.

Pourquoi JSON doit-il utiliser UTF-8 et ne porter aucun BOM ?

La section 8.1 de la RFC 8259 exige que le texte JSON utilisé pour l'échange soit encodé en UTF-8, dit que les implémentations ne doivent pas ajouter de BOM au début, et laisse les parseurs ignorer le BOM pour la robustesse. Un fichier avec BOM est rejeté par JSON.parse avec Unexpected token, même si rien ne semble faux à l'œil. Donc retirez le BOM à la lecture et décodez explicitement en UTF-8.

Faut-il utiliser JSON5 ou JSONC pour contourner ces limites de grammaire ?

Oui, mais seulement pour la lecture locale comme les fichiers de configuration ; les données échangées entre systèmes devraient rester du JSON standard. Les contre-exemples montrent que {"a": 1} suivi d'un commentaire est valide en JSON5 et JSONC mais invalide en JSON standard, ce qui explique pourquoi « le fichier de config fonctionne mais l'API le rejette » est presque toujours un problème de dialecte. Lors de la conversion, retirez d'abord les commentaires, guillemets simples et virgules finales et donnez le résultat à un parseur standard.