Co se v řetězci musí escapovat
Řetězec je text ve dvojitých uvozovkách. Smí obsahovat jakýkoli znak Unicode kromě tří skupin, které se escapovat musí:
- uvozovku
", jinak by řetězec skončil, - zpětné lomítko
\, protože začíná escape sekvenci, - řídicí znaky U+0000 až U+001F, tedy hlavně konec řádku a tabulátor.
Všechno ostatní, včetně české diakritiky a emoji, může stát v UTF-8 přímo. Escapovat se smí libovolný znak, nutné to ale není.
Escape sekvence
JSON zná osm dvouznakových sekvencí a obecný zápis \uXXXX:
| Zápis | Znak | Význam |
|---|---|---|
| \" | " | uvozovka |
| \\ | \ | zpětné lomítko |
| \/ | / | lomítko (escapovat se nemusí) |
| \b | U+0008 | backspace |
| \f | U+000C | form feed |
| \n | U+000A | nový řádek |
| \r | U+000D | návrat vozíku |
| \t | U+0009 | tabulátor |
| \u00E9 | é | libovolný znak ze základní roviny Unicode |
| \uD83D\uDE00 | 😀 | znak mimo základní rovinu: náhradní pár UTF-16 |
Za \u následují přesně čtyři šestnáctkové číslice s kódem znaku; velikost
písmen nehraje roli (\u00e9 i \u00E9). Jiné sekvence, které znáš z
programovacích jazyků, JSON nepovoluje: \x41, \' ani
\0.
Emoji a znaky mimo základní rovinu
Čtyři šestnáctkové číslice stačí jen na znaky do U+FFFF. Znak s vyšším kódem, například
většina emoji, se escapuje jako náhradní pár UTF-16: dvě sekvence
\u za sebou. Oba zápisy v ukázce znamenají stejný znak 😀 (U+1F600):
{
"primo": "😀",
"escapovane": "\uD83D\uDE00"
}
Pozor na délku: JavaScript počítá v jednotkách UTF-16, takže "😀".length je 2.
Python i PHP s mb_strlen("😀", "UTF-8") vrátí 1, v UTF-8 má znak 4 bajty. Limit „nejvýš 100
znaků“ proto může v každém jazyce znamenat něco jiného.
Časté chyby v řetězcích
Ukázky ověřené parserem (⏎ značí skutečný konec řádku, ⇥ skutečný tabulátor):
| Zápis | Platný JSON? | Proč | Nástroj |
|---|---|---|---|
| "řádek 1⏎řádek 2" | ne | skutečný konec řádku uvnitř řetězce, správně \n | Vyzkoušet |
| "a⇥b" | ne | skutečný tabulátor, správně \t | Vyzkoušet |
| "C:\Users" | ne | \U není platná escape sekvence, správně "C:\\Users" | Vyzkoušet |
| "\x41" | ne | JSON nezná \x, správně \u0041 | Vyzkoušet |
| 'text' | ne | jednoduché uvozovky | Vyzkoušet |
| "C:\\Users" | ano | zpětné lomítko zdvojené | Vyzkoušet |
| "řádek 1\nřádek 2" | ano | konec řádku jako \n | Vyzkoušet |
Nejčastější příčinou je skládání JSON z kousků textu. Jakmile data obsahují uvozovku nebo zpětné lomítko, výsledek přestane být platný. Řešením je vždy knihovna:
// skládání JSON z řetězců: jedna uvozovka v datech a výsledek je neplatný
const jmeno = 'Pavel "Pája" Novák';
const json = '{"jmeno": "' + jmeno + '"}';
JSON.parse(json); // SyntaxError // knihovna escapuje sama
const json = JSON.stringify({ jmeno: 'Pavel "Pája" Novák' });
// '{"jmeno":"Pavel \\"Pája\\" Novák"}'
JSON.parse(json).jmeno; // 'Pavel "Pája" Novák' Jak řetězce zapisují knihovny
Výstup různých knihoven se liší, i když je ve všech případech platný. Python a PHP ve výchozím nastavení escapují všechny znaky mimo ASCII a PHP navíc i lomítko:
| Jazyk | Výchozí výstup pro "é" a "/" | Jak to změnit |
|---|---|---|
| JavaScript | JSON.stringify: "é" a "/" beze změny | — |
| Python | json.dumps: "\u00e9", lomítko beze změny | ensure_ascii=False |
| PHP | json_encode: "\u00e9" a "\/" | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES |
Na významu dat to nic nemění, parser z "é" i "\u00e9" udělá
stejný řetězec. Escapování mimo ASCII ale zbytečně zvětšuje soubory s češtinou. Escapované
lomítko má jeden praktický účinek: řetězec </script> v datech se zapíše
jako <\/script>, takže neukončí skript, když JSON vložíš přímo do HTML.
Osamocené náhradní znaky
Gramatika JSON dovoluje i zápis, který žádný znak Unicode nekóduje: samotnou polovinu
náhradního páru, třeba "\uDEAD". Vzniká, když program zkrátí text v UTF-16
uprostřed emoji. RFC 8259 chování parserů v tomto případě označuje za nepředvídatelné. Změřené
chování (Node.js 25, Python 3.14 a PHP 8.4):
| Vstup | JavaScript | Python | PHP |
|---|---|---|---|
| "\uDEAD" | řetězec délky 1 | '\udead' | chyba |
Profil I-JSON (RFC 7493) takové řetězce zakazuje. Text proto zkracuj po celých znacích, ne po
jednotkách UTF-16; v JavaScriptu například přes [...text].slice(0, 100).join('').
Porovnávání řetězců
Stejný řetězec lze zapsat různě: "a\\b" a "a\u005Cb" jsou po
zpracování totožné. Řetězce (i názvy v objektech) proto porovnávej až po dekódování parserem,
nikdy v surovém textu JSON. Pozor také na normalizaci Unicode: „é“ může být jeden znak
U+00E9, nebo „e“ a kombinující čárka. JSON je nijak nesjednocuje a porovnání znak po znaku je
vyhodnotí jako různé.