Fare l'escape di una stringa per JSON: tre caratteri sono obbligatori, e uno è una trappola
Pubblicato il 31/07/2026 · 13 min di lettura · Strumenti per sviluppatori
Daniel Okonkwo — Sviluppatore front-end e redattore Tech presso OneKitly
Performance web · Formati di file
Verificato su 4 fonti
La RFC 8259, sezione 7, nomina tre cose che DEVONO essere sottoposte a escape dentro una stringa JSON e nient'altro: le virgolette doppie, la barra rovesciata e ogni carattere di controllo da U+0000 a U+001F. Tutto il resto può restare come UTF-8 letterale: lettere accentate, ideogrammi, emoji, il carattere DEL a U+007F, il separatore di riga a U+2028. Qualsiasi carattere PUÒ essere sottoposto a escape, ed è da lì che viene l'abitudine di scrivere \/ per una barra: JSON non lo richiede mai, e l'unico motivo per farlo è incorporare JSON dentro un tag script HTML, dove la sequenza </ va spezzata. Esistono otto escape di due caratteri — \" \\ \/ \b \f \n \r \t — e tutto il resto usa \uXXXX, quattro cifre esadecimali, una unità UTF-16. Un carattere fuori dal Piano Multilingue di Base ne richiede due: un emoji si scrive come coppia surrogata di dodici caratteri, mai come escape a sei cifre. È lì che sta la trappola. Le stringhe JSON sono sequenze di unità UTF-16, e la grammatica ammette un'unità che è metà di una coppia senza compagna: la specifica lo dice nella sezione 8.2, con � come esempio. Un testo JSON del genere si analizza senza fiatare. Ma la sezione 8.1 esige che il JSON scambiato fra sistemi sia UTF-8, e la RFC 3629, sezione 3, vieta a UTF-8 di codificare qualsiasi cosa fra U+D800 e U+DFFF. Misurato su questo strumento: il testo {"k":"id-�-end"} si analizza, e codificare il risultato in UTF-8 dà i byte 69 64 2d ef bf bd 2d 65 6e 64 — la metà spaiata è diventata U+FFFD, il carattere di sostituzione, e il valore non torna più identico. Nulla ha sollevato un'eccezione. È questo il guasto da cercare.
La RFC 8259 impone l'escape di esattamente tre cose dentro una stringa JSON. Tutto il resto è facoltativo. Ciò che rompe davvero le pipeline è un surrogato spaiato: legale nel testo JSON, impossibile in UTF-8, e sostituito in silenzio non appena i tuoi dati vengono scritti.
Tre obbligatori, tutto il resto facoltativo
La regola è più corta di quanto quasi tutti immaginino. Tutti i caratteri Unicode possono stare fra le virgolette tranne quelli che devono essere sottoposti a escape: le virgolette stesse, la barra rovesciata e i caratteri di controllo da U+0000 a U+001F. Tre voci. Un percorso Windows pieno di barre rovesciate e virgolette, C:\Users\Léa\"relazione".txt, richiede ogni barra rovesciata raddoppiata e ogni virgoletta con escape, e la é resta esattamente com'è. Non c'è alcuna regola sugli accenti, né sulle scritture non latine, né sugli emoji.
Due confini vale la pena verificarli invece di indovinarli. Il carattere DEL a U+007F non è un carattere di controllo a questo fine — l'intervallo si ferma a U+001F — quindi resta letterale, e passarlo nello strumento lo conferma: l'output è il carattere stesso, un byte in UTF-8, senza escape. Il separatore di riga U+2028 e il separatore di paragrafo U+2029 restano ugualmente intatti, perché nemmeno loro stanno nell'intervallo U+0000-U+001F. Un tempo era un pericolo reale quando si incollava JSON dentro uno script in linea, dato che un letterale di stringa JavaScript non poteva contenerli; il linguaggio è stato modificato nel 2019 per permetterlo. Se il tuo output deve ancora sopravvivere a un analizzatore vecchio, la modalità solo-ASCII dello strumento li scappa come \u2028 e \u2029, insieme a tutto ciò che sta oltre l'intervallo ASCII.
La barra con escape merita un paragrafo, perché è la cosa più comune che si fa senza sapere perché. JSON permette \/ e non lo richiede mai. L'abitudine viene dal mettere JSON dentro un elemento script HTML, dove un </script> letterale dentro una stringa chiuderebbe l'elemento in anticipo e consegnerebbe il resto dei tuoi dati all'analizzatore HTML; fare l'escape della barra spezza la sequenza e il browser non la vede mai. Lo strumento ha un interruttore apposito, disattivo per difetto, che è il valore giusto: attivalo quando incorpori, lascialo spento in tutto il resto. Fare l'escape delle barre in una risposta API ingrassa il payload e non cambia un solo valore.
Unità UTF-16, non caratteri
L'escape \uXXXX porta esattamente quattro cifre esadecimali, cioè un'unità UTF-16, e copre da U+0000 a U+FFFF. Tutto ciò che sta sopra va scritto con due di essi, e la specifica è esplicita: per fare l'escape di un carattere esteso che non sta nel Piano Multilingue di Base, esso è rappresentato come una sequenza di dodici caratteri che codifica la coppia surrogata UTF-16. Non esiste una forma a sei cifre. Passa una faccina sorridente nello strumento in modalità ASCII e produce 😀, che è corretto; se un giorno vedi \u1f600 nell'output di qualcuno, quello non è JSON, è un'altra convenzione di escape entrata da Python o da una shell.
È per questo che lo strumento mostra quattro conteggi diversi per la stessa stringa ed è per questo che non concordano. Una faccina sorridente è un punto di codice, due unità UTF-16 e quattro byte UTF-8. Un emoji di famiglia fatto di quattro figure unite da giunzioni a larghezza zero sono sette punti di codice, undici unità UTF-16 e venticinque byte UTF-8. Se la tua colonna di database è dichiarata a venti caratteri, quale di quei tre numeri significhi è una proprietà del database e non dei tuoi dati, e nello scarto fra loro vivono gli errori di troncamento.
Il surrogato spaiato, e come finisce nei tuoi dati
Una coppia surrogata sono due unità di codice che significano qualcosa solo insieme: una metà alta da U+D800 a U+DBFF seguita da una metà bassa da U+DC00 a U+DFFF. La grammatica JSON non impone l'accoppiamento. La sezione 8.2 della RFC 8259 lo dice a chiare lettere, notando che la specifica consente ai valori di stringa di contenere sequenze di bit che non possono codificare caratteri Unicode, e offrendo � come esempio. Perciò {"k":"id-�-end"} è un testo JSON sintatticamente valido. Ogni analizzatore in cui ti imbatterai lo accetta.
La contraddizione arriva una sezione prima. La sezione 8.1 esige che il testo JSON scambiato fra sistemi che non fanno parte di un ecosistema chiuso sia codificato in UTF-8, e la RFC 3629, che definisce UTF-8, vieta di codificare numeri di carattere fra U+D800 e U+DFFF, proprio perché sono riservati a UTF-16. Dunque un valore che JSON permette non può esprimersi nella codifica che JSON impone. Ciò che le implementazioni fanno invece di fallire è sostituire: fai passare quel testo JSON in un analizzatore e ricodifica il risultato, e i byte tornano come 69 64 2d ef bf bd 2d 65 6e 64. Quei tre byte centrali, EF BF BD, sono U+FFFD, il carattere di sostituzione. Il valore entrato non è il valore uscito, e da nessuna parte è stato sollevato un errore.
Nessuno digita un surrogato spaiato. Arrivano dal troncamento. Prendi la stringa Rapport 📊 final, sedici unità UTF-16 per quindici punti di codice, e tagliala a nove unità perché stia in un'etichetta: il risultato è Rapport seguito da mezzo emoji, che lo strumento scappa come Rapport �. È questa l'origine quotidiana: una colonna di database con un limite di caratteri, un'interfaccia che accorcia un titolo, una riga di log tagliata a larghezza fissa, un'importazione che copia campi di lunghezza fissa. Ovunque una stringa venga tagliata contando unità invece che punti di codice, il taglio può cadere in mezzo a una coppia.
Lo strumento ti dà due modi per individuarne uno prima che parta. L'escaper scrive sempre un surrogato spaiato nella forma \uXXXX, anche nella sua modalità predefinita non-ASCII — esattamente ciò che fa un JSON.stringify moderno — quindi un � o un � inatteso nell'output è il segnale. E il contatore di byte, che dà gli stessi numeri di un vero codificatore UTF-8, avrà già contato quella metà come tre byte: tre byte è quanto costa U+FFFD, così una stringa la cui forma con escape contiene un surrogato spaiato è già addebitata per un carattere di sostituzione prima ancora di lasciare il tuo schermo.
Dove questo strumento è deliberatamente più permissivo di un analizzatore
Nella direzione dell'escape, l'output predefinito dello strumento coincide carattere per carattere con quanto produce un JSON.stringify moderno, su ogni caso provato: caratteri di controllo, emoji, accenti combinanti, percorsi Windows, surrogati spaiati. Se usi solo le impostazioni predefinite, il risultato è il corpo di una stringa JSON senza le virgolette esterne, e niente di più sorprendente.
Nella direzione inversa è deliberatamente più lasco, e sapere dove conta. Un vero analizzatore JSON rifiuta un carattere di controllo grezzo dentro una stringa: incolla un carattere di campanello letterale in un documento JSON e ottieni un errore di sintassi che lo dice. Questo strumento lo accetta e te lo restituisce, con il ragionamento che stai ispezionando un frammento e non validando un documento. Far passare una stringa nella modalità inversa senza lamentele non prova quindi che il JSON attorno sia valido. Rifiuta comunque le tre cose che rendono un frammento privo di senso: un escape sconosciuto come \x, un \u incompleto o non esadecimale, e una barra rovesciata penzolante proprio alla fine, e per ciascuna segnala la posizione e la sequenza incriminata.
L'interruttore solo-ASCII è l'altra impostazione da capire, perché cambia la dimensione del tuo payload e non il suo significato. Fare l'escape di tutto ciò che supera l'intervallo ASCII rende l'output trasportabile attraverso un sistema dalla codifica incerta, a un costo reale: Café passa da quattro caratteri a nove, e un singolo emoji da due a dodici. Usalo quando il trasporto è dubbio — un vecchio aggregatore di log, un URL, un'intestazione, un database di cui non conosci la collazione — e non altrimenti, perché UTF-8 è ciò che la sezione 8.1 chiede ed è più compatto.
| Ingresso | Richiesto dalla RFC 8259? | Output dello strumento (modalità predefinita) |
|---|---|---|
| Una virgoletta doppia | Sì, obbligatorio | Escape di due caratteri |
| Una barra rovesciata | Sì, obbligatorio | Raddoppiata |
| Una tabulazione, U+0009 | Sì, obbligatorio (carattere di controllo) | La forma breve della tabulazione, non un escape di sei caratteri |
| Il carattere di campanello, U+0007 | Sì, obbligatorio (controllo senza forma breve) | Un escape di sei caratteri che finisce in 0007 |
| DEL, U+007F | No — l'intervallo di controllo si ferma a U+001F | Lasciato letterale, un byte UTF-8 |
| Una barra | No — può avere l'escape, mai obbligatorio | Lasciata com'è salvo che l'opzione barra sia attiva |
| Un emoji fuori dal BMP | No — l'UTF-8 letterale va bene | Mantenuto com'è; in modalità ASCII, una coppia surrogata di dodici caratteri |
| Una metà surrogata spaiata | Rappresentabile, ma non codificabile in UTF-8 | Sempre con escape come \uXXXX, anche in modalità predefinita — è il segnale d'allarme |
Domande frequenti
- Quali caratteri devo sottoporre a escape in una stringa JSON?
- Esattamente tre specie, secondo la RFC 8259 sezione 7: le virgolette doppie, la barra rovesciata e ogni carattere di controllo da U+0000 a U+001F. Nient'altro è obbligatorio. Lettere accentate, ideogrammi, emoji, DEL a U+007F e i separatori U+2028 e U+2029 possono stare tutti nella stringa come UTF-8 letterale. Dei caratteri di controllo, cinque hanno forme brevi — backspace, avanzamento pagina, avanzamento riga, ritorno a capo e tabulazione — e gli altri richiedono la forma \u a sei caratteri, che è come si scrive un campanello o un byte nullo. Qualsiasi cosa può avere l'escape se vuoi, ed è per questo che a volte arriva JSON perfettamente valido con ogni carattere non ASCII sillabato: è più grande, non più corretto.
- Come scrivo un emoji in una stringa JSON?
- Due modi, entrambi corretti. Lascialo come UTF-8 letterale: nulla nella specifica impone l'escape, ed è ciò che lo strumento fa per difetto. Oppure, se il payload deve essere ASCII, scrivi la coppia surrogata: due escape \uXXXX, dodici caratteri in tutto, uno per la metà alta e uno per quella bassa. Non esiste una forma a sei cifre: \u1f600 non è JSON, e un analizzatore che lo accetti non sta facendo ciò che dice lo standard. Il costo della forma ASCII è reale — un emoji passa da due caratteri a dodici — quindi usala solo quando al trasporto non si può affidare UTF-8.
- Che cos'è un surrogato spaiato e perché rompe le cose?
- È metà di una codifica a due unità senza compagna: un'unità di codice fra U+D800 e U+DFFF che sta da sola. JSON lo permette — la RFC 8259 sezione 8.2 riconosce esplicitamente che i valori di stringa possono contenere sequenze di bit incapaci di codificare caratteri Unicode, e porta un surrogato spaiato come esempio — ma UTF-8 non riesce a esprimerlo, perché la RFC 3629 vieta di codificare qualsiasi cosa in quell'intervallo. Poiché la sezione 8.1 esige UTF-8 per lo scambio, le due regole si scontrano, e ciò che accade in pratica è una sostituzione: la metà diventa U+FFFD, il carattere di sostituzione, tre byte, e il valore originale è sparito. Lungo la strada non viene sollevata alcuna eccezione, ed è per questo che la cosa riemerge giorni dopo come una ricerca che non trova più nulla o un identificatore che non aggancia più.
- Devo fare l'escape della barra?
- Solo quando incorpori il JSON dentro un elemento script HTML. JSON permette \/ e non lo richiede mai; l'escape esiste perché un </script> letterale dentro un valore di stringa chiuderebbe l'elemento in anticipo e consegnerebbe il resto dei tuoi dati all'analizzatore HTML come markup. Spezzare la sequenza con una barra rovesciata lo impedisce. Ovunque altrove — una risposta API, un file su disco, un messaggio in coda — fare l'escape delle barre gonfia soltanto il payload. Lo strumento arriva con l'opzione disattiva, che è il valore giusto, e attivarla non cambia nulla del significato del valore una volta analizzato.
- Lo strumento ha accettato la mia stringa ma il mio analizzatore la rifiuta. Perché?
- Perché la direzione inversa è deliberatamente più tollerante di un analizzatore di documenti, e la differenza sono i caratteri di controllo. Un analizzatore JSON rifiuta un carattere di controllo grezzo posto letteralmente dentro una stringa — il messaggio di solito dice «bad control character in string literal» — mentre questo strumento lo accetta e te lo restituisce, presumendo che tu stia ispezionando un frammento e non validando un documento. Se il tuo analizzatore protesta e lo strumento no, cerca una tabulazione o un a capo grezzi che avrebbero dovuto essere scritti come escape. Lo strumento rifiuta comunque i tre difetti che rendono illeggibile un frammento: una sequenza di escape sconosciuta, un \u incompleto o non esadecimale, e una barra rovesciata penzolante alla fine, e per ciascuno ti indica la posizione.
Articoli che potrebbero interessarti
Tutte le guide →Strumenti correlati
Questo descrive il comportamento di un formato di file e di un motore di rendering, verificato sulla specifica citata e sul codice dello strumento così com'è oggi. I motori non concordano: GitHub, GitLab, un generatore di siti statici e l'anteprima del tuo editor sono quattro implementazioni diverse, e ciò che funziona in una può non funzionare in un'altra. Niente qui è una garanzia sulla tua catena di pubblicazione: prova il risultato dove verrà davvero pubblicato, e tratta ogni strumento, questo compreso, come qualcosa da verificare e non da credere.
Fonti
- RFC Editor — RFC 8259, The JavaScript Object Notation (JSON) Data Interchange Format, December 2017, Internet Standard STD 90 — section 7 lists the characters that MUST be escaped (quotation mark, reverse solidus, U+0000 to U+001F), states that any character MAY be escaped, and defines the \uXXXX form and the twelve-character surrogate pair; section 8.1 requires UTF-8 for interchange; section 8.2 acknowledges that the grammar permits an unpaired surrogate such as �
- RFC Editor — RFC 3629, UTF-8, a transformation format of ISO 10646, November 2003 — section 3 prohibits UTF-8 from encoding character numbers between U+D800 and U+DFFF because they are reserved for use with the UTF-16 encoding form
- Ecma International — ECMA-404, The JSON Data Interchange Syntax, 2nd edition, December 2017 — the syntax half of the same standard, published in parallel with RFC 8259 and freely downloadable
- Ecma International — ECMAScript Language Specification, JSON.stringify — since the well-formed JSON.stringify change of ES2019, a lone surrogate in a JavaScript string is emitted as a \uXXXX escape rather than as an unpaired code unit, which is the behaviour this tool reproduces
Hai notato un errore in questo articolo?