FAQ su sintassi e formati: commenti, virgole finali e precisione
In sintesi: Se i commenti sono ammessi, perché una virgola finale dà errore, se le chiavi devono essere tra virgolette, perché 9007199254740993 cambia — dodici conclusioni, ciascuna verificata rispetto a RFC 8259.

In sintesi: scrivi JSON semplice secondo RFC 8259 — virgolette doppie, niente commenti, niente virgole finali, UTF-8 senza BOM — per qualsiasi cosa che attraversi il confine di un processo; usa JSONC o JSON5 per le configurazioni che richiedono commenti e riconvertile a runtime; e serializza lato server come stringhe gli interi superiori a 2^53-1.
Ciò che accomuna queste domande: sembrano cose che "dovrebbero funzionare", ma in realtà la specifica è molto severa.
1. Cosa conta come JSON valido
Qual è la differenza tra JSON, JSON5 e JSONC?
Il JSON standard riconosce solo stringhe con virgolette doppie e non ammette né commenti né virgole finali. JSON5 e JSONC aggiungono entrambi funzionalità sopra di esso:
| Funzionalità | JSON (RFC 8259) | JSONC | JSON5 |
|---|---|---|---|
| Stringhe con virgolette doppie | Supportate | Supportate | Supportate |
| Stringhe con virgolette singole | Non supportate | Non supportate | Supportate |
| Commenti | Non supportati | Supportati | Supportati |
| Virgole finali | Non supportate | Non supportate | Supportate |
| Chiavi senza virgolette | Non supportate | Non supportate | Supportate |
| Numeri esadecimali | Non supportati | Non supportati | Supportati |
NaN / Infinity |
Non supportati | Non supportati | Supportati |
| Estensione del file | .json |
.jsonc |
.json5 |
Il principio di scelta è semplice: usa sempre JSON standard attraverso i confini di processo, mentre i file di configurazione possono usare JSONC o JSON5 in base a ciò che supporta il tuo editor.
tsconfig.json e .vscode/settings.json possono contenere commenti perché l'editor li analizza come JSONC; passarli a un
JSON.parse rigoroso dà comunque errore.
JSON può contenere commenti?
No. La grammatica di RFC 8259 non ha alcuna produzione per i commenti, quindi qualsiasi // o /* */ fa fallire il parsing.
Se vuoi mantenere spiegazioni nella configurazione, esistono tre alternative:
{
"_comment": "以下超时单位是毫秒",
"timeout": 3000,
"retry": {
"$doc": "重试次数,0 表示不重试",
"count": 3
}
}
- Usa campi convenzionali come
_commentche il lettore semplicemente ignora; - Usa campi con prefisso
$per ridurre la probabilità di collisione con i campi di business; - Passa semplicemente a JSONC o YAML per la configurazione e converti in JSON standard a runtime.
Perché una virgola finale causa un errore?
Perché nella grammatica sia gli array sia gli oggetti sono strutture "elemento + separatore", e nessuna virgola è ammessa dopo l'ultimo elemento.
{
"a": 1,
"b": 2,
}
Lo snippet sopra ha una virgola in più prima di }, e l'errore viene di solito segnalato sulla virgola o sulla parentesi di chiusura a seconda dell'implementazione del parser.
È anche la trappola più facile quando si migra da JavaScript — i letterali oggetto JS ammettono le virgole finali, JSON no.
Sono ammesse le virgolette singole?
No. Le stringhe devono usare virgolette doppie; 'text' è un errore di sintassi.
Se i dati sono stati copiati da repr() di Python, da uno script di shell o da una configurazione scritta a mano, convertili prima con uno strumento
invece di sperare che il parser sia indulgente.
Le chiavi possono essere senza virgolette?
No. I nomi dei membri di un oggetto devono essere stringhe racchiuse tra virgolette doppie.
{ name: "json" }
Quello è un letterale oggetto JavaScript, non JSON. Aggiungendo le virgolette diventa valido:
{ "name": "json" }
Il livello superiore può essere un array o una stringa?
Sì. RFC 8259 ammette qualsiasi valore JSON al livello superiore: oggetti, array, stringhe, numeri, true, false e null sono tutti validi.
Nella pratica ingegneristica, però, si raccomanda di usare al livello superiore solo oggetti o array, per due ragioni:
- Compatibilità: i parser più vecchi (dell'era RFC 4627) richiedono che il livello superiore sia un oggetto o un array;
- Sicurezza: un array al livello superiore ha storicamente comportato un rischio di dirottamento JSON. I browser moderni l'hanno risolto, ma i sistemi più vecchi assumono ancora la convenzione.
Quindi "hello" è JSON valido e anche ["a", "b"] lo è, ma un'API che restituisce {"data": [...]} è più sicura.
2. Codifica e stringhe
Un BOM all'inizio del file interrompe il parsing?
Sì. RFC 8259 richiede esplicitamente che il testo JSON sia trasmesso come UTF-8 e non deve aggiungere un byte order mark (BOM).
Il BOM è la sequenza di tre byte EF BB BF all'inizio assoluto del file. È completamente invisibile, ma occupa la prima posizione della stringa,
quindi il parser conclude che "il primo carattere non è {" e dà errore — e la riga e colonna segnalate sono di solito riga 1, colonna 1, il che sembra incomprensibile.
Il "Salva come UTF-8" del Blocco note di Windows include un BOM per impostazione predefinita. Per controllarlo e rimuoverlo:
# 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
Il JSON.parse del JavaScript moderno tollera un BOM (V8 lo salta internamente), ma le implementazioni rigorose in Java, Go e Python no,
il che è una ragione comune per cui "lo stesso file dà risultati diversi in linguaggi diversi".
I caratteri cinesi devono essere convertiti in \uXXXX?
No. RFC 8259 richiede che il testo JSON sia UTF-8, quindi il cinese può comparire direttamente; {"name": "张三"} è perfettamente valido.
\uXXXX è solo una grafia alternativa equivalente, usata per i canali che non possono rappresentare il carattere direttamente. La regola pratica:
- Corpi delle richieste HTTP, file, database: scrivi il cinese direttamente, risparmiando 3 byte per carattere;
- Header HTTP, parametri URL, incorporamento dentro un'altra stringa: usa gli escape per evitare problemi di codifica.
Gli unici caratteri che devono essere sottoposti a escape sono i caratteri di controllo (da U+0000 a U+001F). Per esempio, un a capo deve essere scritto come \n;
non puoi inserire un a capo letterale dentro una stringa.
Le emoji possono andare direttamente in una stringa?
Sì, ma ricorda che occupano due unità di codice UTF-16.
Le emoji e molti caratteri rari (come 𠮷) cadono fuori dal BMP, quindi in JavaScript "😀".length === 2.
Validare in base al numero di caratteri è quindi facile da sbagliare:
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
Se un limite di lunghezza del nickname viene calcolato con .length, una emoji consuma due slot; se il backend conta i byte, ne consuma quattro.
Quando si allineano le definizioni, specifica chiaramente se intendi code point, code unit o byte.
3. Numeri e valori speciali
Perché l'intero grande 9007199254740993 è diventato 9007199254740992?
Perché il JSON.parse di JavaScript converte i numeri in float a doppia precisione IEEE 754, e il più grande intero sicuro è 2^53-1.
Number.MAX_SAFE_INTEGER === 9007199254740991 // true
9007199254740993 // actually yields 9007199254740992
Qualsiasi numero superiore a 2^53-1 può essere arrotondato nelle cifre finali. Le vittime tipiche sono gli ID utente a 19 cifre generati da algoritmi snowflake, i numeri d'ordine e i timestamp in nanosecondi.
Tre correzioni, in ordine di preferenza:
- Serializza l'ID come stringa sul server:
"id": "9007199254740993", eliminando il problema di precisione alla radice; - Usa una libreria di parsing senza perdita: i sostituti di
JSON.parsepossono preservare gli interi grandi alla lettera, per esempiojson-bigint; - Estrai con un'espressione regolare solo per la visualizzazione: non analizzare l'intero documento, fai corrispondere il campo di destinazione come testo.
Nota che JSON.stringify ha il problema simmetrico: un numero fuori dall'intervallo degli interi sicuri può non conservare il valore originale dopo la serializzazione.
NaN e Infinity possono andare in JSON?
No. La produzione dei numeri JSON ammette solo la notazione decimale, e NaN, Infinity e -Infinity non sono valori JSON validi.
L'approccio di JavaScript è il "degrado silenzioso":
JSON.stringify({ value: NaN }); // '{"value":null}'
JSON.stringify({ value: Infinity }); // '{"value":null}'
Nessun errore, NaN diventa semplicemente null. Questa è la scena classica in cui "il frontend ha inviato NaN e il backend ha ricevuto null".
Quando devi veicolare questa semantica, esprimila esplicitamente con stringhe o valori sentinella concordati:
{ "ratio": null, "ratioState": "not-a-number" }
I numeri possono essere scritti come 1e3 o 0.5?
Sì. I numeri JSON supportano decimali ed esponenti, quindi 1e3, 1E3, 0.5 e -0.5 sono tutti validi.
Ma ci sono restrizioni:
- Non possono iniziare o finire con
.:.5e5.sono entrambi invalidi, quindi scrivi0.5e5; - Niente zeri iniziali:
01è invalido; - Niente esadecimali:
0x1Fè invalido, è un'estensione di JSON5; -0è valido, maJSON.stringify(-0)restituisce0, perdendo il segno.
4. Semantica degli oggetti
Cosa succede con chiavi duplicate?
Il comportamento è definito dall'implementazione; RFC 8259 dice solo che i nomi delle chiavi "dovrebbero" essere univoci e non specifica cosa succede in caso di duplicati.
In pratica la stragrande maggioranza delle implementazioni prende il valore ultimo:
JSON.parse('{"a":1,"a":2}') // { a: 2 }
Ma non puoi farci affidamento. Alcune librerie Java, l'encoding/json di Go e il json di Python differiscono tra le versioni:
alcune prendono il primo valore, alcune danno errore, alcune prendono l'ultimo. Trattare le chiavi duplicate come errore è l'approccio più sicuro tra i linguaggi.
Non puoi trovare chiavi duplicate a occhio — in JSON minificato la stessa chiave può essere a migliaia di caratteri di distanza. Usa uno 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
In cosa si trasforma undefined?
Dipende, e questo è il punto più soggetto a bug:
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
Lo stesso undefined significa "il campo scompare" dentro un oggetto e "diventa null" dentro un array.
Se il backend prende percorsi diversi per "campo mancante" e "campo è null", qui ottieni un'incoerenza.
Quando devi esprimere "nessun valore" in modo esplicito, usa null invece di undefined.
Cosa succede dopo il parsing di {"__proto__": {...}}?
JSON.parse stesso è sicuro: __proto__ è trattato come una normale proprietà propria e non modifica il prototipo.
Ciò che è pericoloso è l'operazione di merge dopo il parsing:
const patch = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, patch); // some implementations pollute the prototype
L'approccio corretto è iterare sulle proprietà proprie e saltare __proto__, constructor e prototype.
I dettagli sono nella FAQ su prestazioni e sicurezza.
5. Riferimento rapido
| Sintassi | Valida | Note |
|---|---|---|
{"a": 1,} |
No | Virgola finale |
{'a': 1} |
No | Virgolette singole |
{a: 1} |
No | Chiave senza virgolette |
{"a": 1} // comment |
No | Commenti |
"hello" |
Sì | Una stringa al livello superiore è valida, ma raramente usata nelle API |
{"a": 1e3} |
Sì | Gli esponenti sono supportati |
{"a": 01} |
No | Zero iniziale |
{"a": NaN} |
No | Letterale invalido; stringify lo trasforma in null |
\uFEFF{"a":1} |
No | Un BOM non fa parte del testo JSON |
{"a":1,"a":2} |
Contestato | Le implementazioni prendono l'ultimo valore, ma non farci affidamento |