← Tutti gli articoli

Carattere di controllo errato nel letterale stringa JSON: correzioni

Tab, newline, byte null e codici ANSI di escape grezzi dentro una stringa JSON innescano questo errore. Impara perché la spec JSON li vieta, come si infilano e come rimuoverli o farne l'escape.

SyntaxError: Bad control character in string literal in JSON at position N significa che c'è un carattere di controllo grezzo e non escape —— una tab, una nuova riga, un carriage return, o qualunque carattere nell'intervallo U+0000–U+001F —— seduto dentro a una stringa JSON. La specifica JSON li proibisce. Questo articolo spiega perché, da dove vengono, e come liberartene.

Quale errore di stringa sto ricevendo?

Cos'è un carattere di controllo?

I primi 32 code point Unicode (U+0000 fino a U+001F) sono caratteri di controllo —— codici di formattazione invisibili ereditati dalle telescriventi. Alcuni familiari:

Code pointNomeEscape JSON
U+0000Null
U+0008Backspace\b
U+0009Tab orizzontale\t
U+000ALine feed (nuova riga)\n
U+000CForm feed\f
U+000DCarriage return\r
U+001BEscape (le sequenze ANSI iniziano qui)

La specifica JSON (RFC 8259 §7) è esplicita: tutti i caratteri in questo intervallo devono essere escape quando appaiono dentro una stringa JSON. Una nuova riga grezza dentro una stringa tra virgolette è un errore di sintassi, non un valore.

Perché le nuove righe grezze rompono JSON

Considera un valore di stringa che contiene un vero carattere di nuova riga:

// Come appaiono i byte (la nuova riga è letterale, non \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

Il parser vede la doppia virgoletta di apertura, legge caratteri, poi incontra un byte 0x0A grezzo. Visto che una stringa JSON dev'essere una singola sequenza ininterrotta tra due doppie virgolette sulla stessa riga logica, la nuova riga nuda termina la stringa in modo inatteso.

La rappresentazione corretta usa la sequenza di escape a due caratteri \n:

// Corretto —— \n è l'escape, non una nuova riga letterale
{"message":"line one\nline two"}

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

Come i caratteri di controllo finiscono nel JSON

1. Costruire stringhe JSON con template literal o concatenazione

const note = `first line
second line`;                   // vera nuova riga dal template literal

const json = `{"note":"${note}"}`; // nuova riga grezza incorporata nella stringa
JSON.parse(json);               // SyntaxError: Bad control character…

// ✓ Soluzione: usa sempre JSON.stringify per i valori, non interpolare mai a mano
const json = JSON.stringify({ note });  // fa escape della nuova riga automaticamente

2. Leggere file e incorporare contenuto così com'è

import fs from 'fs';
const content = fs.readFileSync('notes.txt', 'utf8');
// content può contenere \n, \r\n, \t, ecc.

// ❌ Costruzione manuale di stringa —— incorpora caratteri di controllo grezzi
const json = '{"content":"' + content + '"}';

// ✓ JSON.stringify gestisce tutti gli escape
const json = JSON.stringify({ content });

3. Risposte API da sistemi che non fanno escape dell'output

Alcuni sistemi back-end (script PHP legacy, serializer custom, trigger di database che costruiscono JSON manualmente) emettono valori di campo con nuove righe o tab non escape. Ricevi JSON sintatticamente invalido e response.json() lancia.

Diagnostica loggando la lunghezza del body grezzo e i primi 200 caratteri. Cerca interruzioni di riga visibili dentro a quello che dovrebbe essere un valore di stringa.

4. Copia-incolla da un terminale (sequenze di escape ANSI)

L'output del terminale contiene codici colore ANSI che iniziano col carattere Escape (0x1B) seguito da [. Se incolli l'output del terminale in una stringa JSON, ogni reset di colore (\x1B[0m) diventa un carattere di controllo.

// Output del terminale incollato in una stringa JSON:
{"log":"\u001B[32mOK\u001B[0m request processed"}
//      ^^^ byte ESC grezzo —— dovrebbe essere la sequenza di escape \u001B di 6 caratteri

5. Byte null da dati binari

Leggere parte di un file binario, una colonna BLOB di database, o una stringa stile C dentro un campo JSON può incorporare byte null (U+0000), che sono la fonte più comune di questo errore nelle pipeline da DB a JSON.

Come risolverlo

Prevenzione: usa sempre JSON.stringify

Questa è la soluzione definitiva. JSON.stringify() fa escape correttamente di ogni carattere di controllo —— non devi mai fare escape a mano.

// ✓ Sicuro indipendentemente da cosa contenga userInput
const json = JSON.stringify({ message: userInput });

Riparazione: rimuovi o fai escape a posteriori

Se stai ricevendo JSON rotto da una fonte esterna e non puoi sistemare il produttore, puoi sanificare la stringa grezza prima del parsing. L'approccio più sicuro è fare escape dei caratteri di controllo nudi:

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);

Se i caratteri di controllo sono rumore (codici ANSI, byte null) anziché dati significativi, rimuoverli è più semplice:

// Rimuove tutti i caratteri di controllo eccetto \t, \n, \r (comuni nel testo)
const cleaned = raw.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, '');
const data = JSON.parse(cleaned);

Altre due fonti reali

  • BOM UTF-8 in posizione 0. I file salvati come «UTF-8 con BOM» iniziano con 0xEF 0xBB 0xBF. RFC 8259 proibisce un BOM in JSON, e JSON.parse lo rifiuta con un errore in posizione 0 facile da confondere con un carattere di controllo. Rimuovilo con text.replace(/^/, '') prima del parsing, o salva il file come «UTF-8» (senza BOM) nel tuo editor.
  • VS Code: «Remove Control Characters». Se stai pulendo un singolo file a mano, Selection → Remove All Occurrences of Find Match di VS Code combinato con ricerca regex su [\x00-\x08\x0b\x0c\x0e-\x1f] (preservando i veri \t\n\r) cancella i byte di controllo invisibili in un colpo solo. C'è anche un comando «Remove Control Characters» in alcune estensioni se lo fai spesso.

Individuare la posizione incriminata

Il messaggio di errore include una posizione in byte. Usala per ispezionare cosa c'è lì:

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 rifà l'escape dei caratteri di controllo così li vedi
    // es. "...line one\nline two..." —— quel \n era una nuova riga grezza
  }
}

Domande frequenti

Cos'è un bad control character in JSON?

Un carattere grezzo non-escape nell'intervallo U+0000–U+001F —— un tab, nuova riga, carriage return, byte null, o escape ANSI —— dentro a una stringa JSON. RFC 8259 §7 richiede che ognuno di tali caratteri sia escape (per esempio \n per una nuova riga).

Come risolvo un errore bad control character?

La soluzione definitiva è costruire il JSON con JSON.stringify(), che fa escape dei caratteri di controllo automaticamente. Se ricevi JSON rotto che non puoi sistemare alla sorgente, fai escape o rimuovi i caratteri di controllo con una regex prima del parsing (vedi snippet sopra).

Perché una nuova riga grezza rompe JSON?

Una stringa JSON dev'essere una sequenza ininterrotta tra due doppie virgolette. Un byte letterale di nuova riga (0x0A) termina la stringa prematuramente, perciò il parser segnala un errore di carattere di controllo. La rappresentazione valida è l'escape a due caratteri \n.

Come rimuovo i codici colore ANSI dal JSON?

Le sequenze ANSI iniziano col byte Escape (U+001B). Rimuovile con raw.replace(/\[[0-9;]*m/g, '') prima del parsing, o evita di incollare output grezzo del terminale dentro valori di stringa JSON in prima battuta.

Ripara JSON rotto nel tuo browser

Incolla il tuo JSON rotto in JSON Fix su fixjson.org. Il parser tollerante identifica le violazioni di carattere di controllo e segnala la posizione esatta. Per i casi semplici può riparare la stringa automaticamente.