← Tous les articles

Caractère de contrôle invalide dans un littéral de chaîne JSON : correctifs

Des tabulations, sauts de ligne, octets nuls et codes d'échappement ANSI bruts à l'intérieur d'une chaîne JSON déclenchent cette erreur. Apprenez pourquoi la spec JSON les interdit, comment ils se glissent et comment les supprimer ou les échapper.

SyntaxError: Bad control character in string literal in JSON at position N signifie qu'il y a un caractère de contrôle brut et non échappé —— une tabulation, un saut de ligne, un retour chariot, ou n'importe quel caractère dans la plage U+0000–U+001F —— assis à l'intérieur d'une chaîne JSON. La spécification JSON les interdit. Cet article explique pourquoi, d'où ils viennent, et comment s'en débarrasser.

Quelle erreur de chaîne ai-je ?

  • Bad control character —— un tab/saut de ligne/octet null brut est à l'intérieur d'une chaîne sans échappement.
  • Bad escaped character —— un \ est suivi de quelque chose que JSON n'autorise pas (par ex. \x, chemins Windows).
  • Unterminated string —— une chaîne a été ouverte avec " mais jamais fermée.

Qu'est-ce qu'un caractère de contrôle ?

Les 32 premiers code points Unicode (U+0000 à U+001F) sont des caractères de contrôle —— des codes de formatage invisibles hérités des téléscripteurs. Quelques-uns familiers :

Code pointNomÉchappement JSON
U+0000Null
U+0008Backspace\b
U+0009Tabulation horizontale\t
U+000ALine feed (saut de ligne)\n
U+000CForm feed\f
U+000DCarriage return\r
U+001BEscape (les séquences ANSI commencent ici)

La spécification JSON (RFC 8259 §7) est explicite : tous les caractères de cette plage doivent être échappés quand ils apparaissent à l'intérieur d'une chaîne JSON. Un saut de ligne brut à l'intérieur d'une chaîne entre guillemets est une erreur de syntaxe, pas une valeur.

Pourquoi les sauts de ligne bruts cassent JSON

Imagine une valeur de chaîne contenant un vrai caractère de saut de ligne :

// À quoi ressemblent les octets (le saut de ligne est littéral, pas \n) :
{"message":"line one
line two"}

JSON.parse('{"message":"line one\nline two"}')
// SyntaxError: Bad control character in string literal in JSON at position 20

Le parser voit le guillemet double d'ouverture, lit des caractères, puis tombe sur un octet 0x0A brut. Comme une chaîne JSON doit être une séquence ininterrompue entre deux guillemets doubles sur la même ligne logique, le saut de ligne nu termine la chaîne de manière inattendue.

La représentation correcte utilise la séquence d'échappement à deux caractères \n :

// Correct —— \n est l'échappement, pas un vrai saut de ligne
{"message":"line one\nline two"}

JSON.parse('{"message":"line one\nline two"}') // ✓ fonctionne

Comment les caractères de contrôle entrent dans JSON

1. Construire des chaînes JSON avec des template literals ou de la concaténation

const note = `first line
second line`;                   // vrai saut de ligne depuis le template literal

const json = `{"note":"${note}"}`; // saut de ligne brut intégré dans la chaîne
JSON.parse(json);               // SyntaxError: Bad control character…

// ✓ Solution : utilise toujours JSON.stringify pour les valeurs, n'interpole jamais à la main
const json = JSON.stringify({ note });  // échappe le saut de ligne automatiquement

2. Lire des fichiers et intégrer le contenu tel quel

import fs from 'fs';
const content = fs.readFileSync('notes.txt', 'utf8');
// content peut contenir \n, \r\n, \t, etc.

// ❌ Construction manuelle de chaîne —— intègre des caractères de contrôle bruts
const json = '{"content":"' + content + '"}';

// ✓ JSON.stringify gère tout l'échappement
const json = JSON.stringify({ content });

3. Réponses d'API de systèmes qui n'échappent pas leur sortie

Certains systèmes back-end (scripts PHP legacy, sérialiseurs maison, triggers de base de données qui construisent du JSON à la main) émettent des valeurs de champ avec des sauts de ligne ou des tabulations non échappés. Tu reçois du JSON syntaxiquement invalide et response.json() lance.

Diagnostique en loggant la longueur du body brut et les 200 premiers caractères. Cherche des sauts de ligne visibles à l'intérieur de ce qui devrait être une valeur de chaîne.

4. Copier-coller depuis un terminal (séquences d'échappement ANSI)

La sortie du terminal contient des codes couleur ANSI qui commencent par le caractère Escape (0x1B) suivi de [. Si tu colles la sortie du terminal dans une chaîne JSON, chaque reset de couleur (\x1B[0m) devient un caractère de contrôle.

// Sortie de terminal collée dans une chaîne JSON :
{"log":"\u001B[32mOK\u001B[0m request processed"}
//      ^^^ octet ESC brut —— devrait être la séquence d'échappement \u001B de 6 caractères

5. Octets nuls depuis des données binaires

Lire une partie d'un fichier binaire, une colonne BLOB de base de données, ou une chaîne de style C dans un champ JSON peut intégrer des octets nuls (U+0000), qui sont la source la plus commune de cette erreur dans les pipelines BDD vers JSON.

Comment le corriger

Prévention : utilise toujours JSON.stringify

C'est la solution définitive. JSON.stringify() échappe correctement chaque caractère de contrôle —— tu n'as jamais besoin d'échapper à la main.

// ✓ Sûr peu importe ce que contient userInput
const json = JSON.stringify({ message: userInput });

Réparation : enlève ou échappe après coup

Si tu reçois du JSON cassé depuis une source externe et ne peux pas corriger le producteur, tu peux nettoyer la chaîne brute avant le parsing. L'approche la plus sûre est d'échapper les caractères de contrôle nus :

function escapeControlChars(raw) {
  return raw.replace(
    /[\u0000-\u001F]/g,
    (ch) => '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0')
  );
}

const fixed = escapeControlChars(rawFromApi);
const data  = JSON.parse(fixed);

Si les caractères de contrôle sont du bruit (codes ANSI, octets nuls) plutôt que des données significatives, les enlever est plus simple :

// Enlève tous les caractères de contrôle sauf \t, \n, \r (courants dans le texte)
const cleaned = raw.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '');
const data = JSON.parse(cleaned);

Deux autres sources du monde réel

  • BOM UTF-8 en position 0. Les fichiers enregistrés en « UTF-8 avec BOM » commencent par 0xEF 0xBB 0xBF. RFC 8259 interdit un BOM dans JSON, et JSON.parse le rejette avec une erreur en position 0 facilement confondue avec un caractère de contrôle. Enlève-le avec text.replace(/^/, '') avant le parsing, ou enregistre le fichier en « UTF-8 » (sans BOM) dans ton éditeur.
  • VS Code : « Remove Control Characters ». Si tu nettoies un seul fichier à la main, le Selection → Remove All Occurrences of Find Match de VS Code combiné à une recherche regex sur [\x00-\x08\x0b\x0c\x0e-\x1f] (en préservant les vrais \t\n\r) supprime les octets de contrôle invisibles en une passe. Il y a aussi une commande « Remove Control Characters » dans certaines extensions si tu fais ça souvent.

Détecter la position fautive

Le message d'erreur inclut une position en octets. Utilise-la pour inspecter ce qui s'y trouve :

try {
  JSON.parse(raw);
} catch (e) {
  const pos = Number(e.message.match(/position (\d+)/)?.[1]);
  if (!isNaN(pos)) {
    const context = raw.slice(Math.max(0, pos - 20), pos + 20);
    console.log('Context around error:', JSON.stringify(context));
    // JSON.stringify ré-échappe les caractères de contrôle pour que tu les voies
    // par ex. "...line one\nline two..." —— ce \n était un vrai saut de ligne
  }
}

Foire aux questions

Qu'est-ce qu'un bad control character en JSON ?

Un caractère brut et non échappé dans la plage U+0000–U+001F —— un tab, saut de ligne, retour chariot, octet null, ou échappement ANSI —— à l'intérieur d'une chaîne JSON. RFC 8259 §7 exige que chacun de ces caractères soit échappé (par exemple \n pour un saut de ligne).

Comment corriger une erreur bad control character ?

La correction définitive est de construire le JSON avec JSON.stringify(), qui échappe les caractères de contrôle automatiquement. Si tu reçois du JSON cassé que tu ne peux pas corriger à la source, échappe ou enlève les caractères de contrôle avec une regex avant le parsing (voir les snippets ci-dessus).

Pourquoi un saut de ligne brut casse JSON ?

Une chaîne JSON doit être une séquence ininterrompue entre deux guillemets doubles. Un octet de saut de ligne littéral (0x0A) termine la chaîne prématurément, donc le parser signale une erreur de caractère de contrôle. La représentation valide est l'échappement à deux caractères \n.

Comment enlever les codes couleur ANSI de JSON ?

Les séquences ANSI commencent par l'octet Escape (U+001B). Enlève-les avec raw.replace(/\[[0-9;]*m/g, '') avant le parsing, ou évite carrément de coller la sortie brute du terminal dans des valeurs de chaîne JSON.

Réparer du JSON cassé dans ton navigateur

Colle ton JSON cassé dans JSON Fix sur fixjson.org. Le parser indulgent identifie les violations de caractères de contrôle et signale la position exacte. Pour les cas simples, il peut réparer la chaîne automatiquement.