I dieci errori JSON che rompono davvero un payload
Ordinati per frequenza, non per quanto sono interessanti, con il messaggio d’errore esatto che ciascuno produce.
Ogni affermazione di questa pagina è misurata oppure ha una fonte. Quando non è né l’una né l’altra, la pagina lo dice.
Quasi ogni ticket «JSON non valido» è una di dieci cose. Il parser ti dà una posizione, ogni tanto un carattere, e mai la causa. Eccole in ordine approssimativo di frequenza, con il messaggio che avrai visto e la correzione.
1. Virgole finali
Di gran lunga il più comune, perché ogni altro formato che scrivi tutto il giorno le ammette.
{ "a": 1, "b": 2, }
V8 ti restituisce un messaggio che non nomina né le virgole né quello che hai fatto:
Expected double-quoted property name in JSON at position 18 (line 1 column 19)
Il parser ha consumato la virgola, si aspettava un’altra chiave e ha trovato }. Ora fai la stessa cosa in un array:
Unexpected token ']', "[1,2,]" is not valid JSON
Formulazione completamente diversa per lo stesso identico errore, perché nel parser il percorso degli array fallisce su un’altra produzione della grammatica. Se stai cercando la stringa dell’errore per capire cos’è successo, quell’asimmetria è il motivo per cui non trovi nulla di utile.
Python è più diretto, ma solo di recente. Su 3.13 e successivi:
Illegal trailing comma before end of object
Su 3.12 e precedenti lo stesso input dà Expecting property name enclosed in double quotes: line 1 column 19 (char 18). Stesso interprete, stesso difetto nel tuo file, due spiegazioni diverse a seconda della versione che la tua CI si trova a fissare.
Correzione: cancella la virgola. { "a": 1, "b": 2 }.
2. Apici singoli
Un dict Python passato per print() o str() invece che per json.dumps():
{'ok': True}
Quello non è JSON e non lo è mai stato. Fallisce sul primo apice:
Expected property name or '}' in JSON at position 1 (line 1 column 2)
Nota anche True, che è un secondo fallimento, distinto, in attesa dietro il primo. I booleani JSON sono minuscoli.
{"ok": true}
Correggi alla fonte: json.dumps(obj), e se l’output finisce in un file UTF-8 o in un corpo HTTP, json.dumps(obj, ensure_ascii=False) così i caratteri accentati restano leggibili invece di diventare escape \uXXXX. Aggiungi separators=(",", ":") se lo vuoi compatto.
3. Chiavi senza virgolette
Un letterale di oggetto JavaScript incollato dritto in un campo JSON:
{ name: "ada", active: true }
Expected property name or '}' in JSON at position 2 (line 1 column 3)
JSON richiede che ogni chiave sia una stringa tra virgolette doppie. Non tra apici, non nuda, non un numero. {"name": "ada", "active": true}. È la stessa classe di incollaggio del punto precedente e la correzione è la stessa: tira fuori il valore dal runtime con un serializzatore vero, non da un log della console.
4. Caratteri di controllo senza escape
Un a capo vero dentro un letterale di stringa:
{"note": "line one
line two"}
Bad control character in string literal in JSON at position 18 (line 1 column 19)
Python lo chiama Invalid control character at: line 1 column 19 (char 18). In ogni caso il parser ti sta dicendo che un carattere sotto U+0020 è comparso dentro una stringa, dove è ammesso solo il suo escape.
{"note": "line one\nline two"}
Le tabulazioni sono lo stesso problema e sono più difficili da vedere, perché una tabulazione incollata in un valore sembra spazi. La causa profonda è quasi sempre JSON assemblato per concatenazione di stringhe, dove un campo che contiene un a capo viene inserito tale e quale. L’ultima sezione affronta la cosa come si deve.
5. Percorsi Windows
{"path": "C:\Users\ada\config.json"}
\U e \a non sono escape validi. Python è esplicito: Invalid \escape: line 1 column 13 (char 12). V8 dice Bad escaped character in JSON at position 13 (line 1 column 14), puntando alla U anziché alla barra rovesciata. I nove escape leciti sono \" \\ \/ \b \f \n \r \t e \uXXXX. Tutto il resto è un errore, il che è la progettazione giusta e sorprende di continuo.
{"path": "C:\\Users\\ada\\config.json"}
Le barre normali funzionano benissimo su Windows in quasi tutte le API, e non ti costano barre rovesciate raddoppiate. Se hai un blocco di testo da incorporare e preferisci non farlo a mano, ci pensa lo strumento di escape, e l’unescape fa il percorso inverso.
6. Caratteri invisibili
Questo è quello che ti mangia un pomeriggio. Due varianti:
Spazio unificatore (U+00A0). Copia un frammento da una pagina di documentazione, da un client di chat o da un PDF e gli spazi tra i token potrebbero non essere spazi. La RFC 8259 ammette esattamente quattro caratteri di spaziatura tra i token: spazio, tabulazione, ritorno a capo e avanzamento di riga. U+00A0 non è tra questi, quindi è un errore di sintassi, e si rende identico al carattere che gli sta accanto.
Virgolette tipografiche. Word e Google Docs correggono automaticamente la virgoletta dritta U+0022 nella coppia tipografica U+201C e U+201D mentre scrivi. JSON accetta solo U+0022. Un documento che sullo schermo sembra perfettamente virgolettato non contiene alcun delimitatore di stringa.
Nessuna delle due varianti produce un messaggio che nomini il codepoint. A seconda di dove cade il carattere ottieni Expected double-quoted property name in JSON at position 8, oppure un Unexpected token ' ' che ti ristampa un carattere che non distingui da uno spazio normale. Incolla il documento nel validatore: nomina il carattere e il suo codepoint all’offset esatto, che è il modo più rapido di trovarlo. Riparare JSON li rimuove e ti dice cosa ha tolto.
7. Commenti
{
// il nome visualizzato dell’utente
"name": "ada"
}
V8 segnala Expected property name or '}' in JSON at position 4 (line 2 column 3). Python si ferma con Expecting property name enclosed in double quotes: line 2 column 3 (char 4). Entrambi puntano alla barra, e nessuno dei due pronuncia la parola commento, così il messaggio si legge come un problema di virgolette su una riga che non contiene stringhe.
JSON non ha una sintassi per i commenti. Crockford l’ha rimossa apposta, perché la gente usava i commenti per trasportare direttive di parsing. Se controlli il consumatore, JSONC (quello che VS Code usa per le proprie impostazioni) ammette commenti e virgole finali, e JSON5 ammette parecchio di più. Se non lo controlli, sposta la prosa in un campo, oppure nello schema, dove le descrizioni stanno di casa. L’argomentazione completa vale dieci minuti se stai scegliendo un formato di configurazione.
8. NaN e Infinity
{"ratio": NaN}
Unexpected token 'N', "{"ratio": NaN}" is not valid JSON
La trappola è che Python lo emette per impostazione predefinita. json.dumps({"ratio": float("nan")}) produce {"ratio": NaN} e non solleva nulla, perché l’encoder di CPython è deliberatamente permissivo e il suo stesso decoder riaccetta il valore. Ogni consumatore non Python lo rifiuta.
json.dumps(obj, allow_nan=False) # solleva ValueError invece di spedire JSON non valido
Attivalo oggi nel tuo strato di serializzazione. Un NaN che arriva in produzione è una divisione che non hai protetto, e preferisci trovarla all’encoder piuttosto che nel parser di un cliente.
9. Chiavi duplicate
{"id": 1, "id": 2}
Nessun errore. La RFC 8259 dice che le chiavi DOVREBBERO essere uniche e lascia il comportamento indefinito quando non lo sono. JavaScript e Python tengono entrambi l’ultima, quindi questo si analizza come {"id": 2} e il tuo primo valore sparisce senza lasciare traccia. Altri parser tengono la prima, e alcuni sollevano un errore. È l’unica voce dell’elenco che resta muta, il che la rende la peggiore. Passa un payload nel validatore, che segnala i duplicati invece di collassarli in silenzio.
10. Numeri
Due fallimenti si dividono questo posto.
Zeri iniziali. {"code": 007} non è valido. La grammatica JSON ammette un singolo 0, oppure una cifra da 1 a 9 seguita da altre cifre, e nient’altro. Un CAP, un prefisso internazionale o un codice articolo con lo zero davanti è una stringa. {"code": "007"}.
Interi sopra 2^53-1. {"id": 12345678901234567890} si analizza senza problemi e torna come un numero diverso, perché JavaScript lo memorizza come double IEEE 754 e Number.MAX_SAFE_INTEGER vale 9007199254740991. Nessun errore, nessun avviso, record sbagliato. Manda gli ID grandi come stringhe; la versione lunga spiega perché ogni altra correzione è un ripiego.
La correzione strutturale
Metà di questo elenco (i punti 2, 3, 4 e 5) nasce dalla stessa abitudine: produrre JSON con qualcosa che non è un serializzatore, di solito una concatenazione di stringhe o un log della console.
# ognuno di questi è un bug che aspetta l’input giusto
body = '{"note": "' + note + '", "path": "' + path + '"}'
Un a capo in note lo rompe. Una barra rovesciata in path lo rompe. Una virgoletta in uno dei due lo rompe, e se quell’input veniva da un utente è un’iniezione, non un problema di formattazione.
body = json.dumps({"note": note, "path": path}, allow_nan=False)
Il serializzatore fa l’escape di ciò che va sottoposto a escape, mette le virgolette dove servono e rifiuta ciò che non è rappresentabile. Non è una preferenza di stile. Comporre JSON a mano significa reimplementare correttamente le regole di escape della sezione 7 della RFC 8259 in ogni ramo, e nessuno lo fa.
Quando quello che ti consegnano è un documento rotto e non un produttore rotto, Riparare JSON applica le correzioni qui sopra e stampa l’elenco di ogni modifica fatta, così puoi vedere se ha tirato a indovinare da qualche parte prima di fidarti dell’output.