JSON Toolkit

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.

Principiante~12 min di letturaPubblicato il 2026-10-30Aggiornato il 2026-10-30
Copertina della FAQ su sintassi e formati

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 _comment che 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:

  1. Compatibilità: i parser più vecchi (dell'era RFC 4627) richiedono che il livello superiore sia un oggetto o un array;
  2. 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:

  1. Serializza l'ID come stringa sul server: "id": "9007199254740993", eliminando il problema di precisione alla radice;
  2. Usa una libreria di parsing senza perdita: i sostituti di JSON.parse possono preservare gli interi grandi alla lettera, per esempio json-bigint;
  3. 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 .: .5 e 5. sono entrambi invalidi, quindi scrivi 0.5 e 5;
  • Niente zeri iniziali: 01 è invalido;
  • Niente esadecimali: 0x1F è invalido, è un'estensione di JSON5;
  • -0 è valido, ma JSON.stringify(-0) restituisce 0, 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