Čtení hranic gramatiky JSON z RFC 8259
Shrnutí: Převod gramatických produkcí RFC 8259 do srozumitelného jazyka – které znaky smějí být přímo, které musí být escapované a kde specifikace záměrně ponechává prostor.

Stručně: když se objeví syntaktická chyba JSON, najděte první neplatný znak a zařaďte ho jako jednoduchou uvozovku, koncovou čárku, komentář nebo řídicí znak, místo abyste znovu četli celý dokument; na straně kódování ověřte UTF-8 bez BOM a celá čísla nad 2^53 - 1 přenášejte jako řetězce.
Vlastní text specifikace má jen několik stránek, přesto se za každým „MUST“, „SHOULD“ a „MAY“ skrývá incident, který kdysi držel tým vzhůru celou noc.
1. Jak souvisí tři texty
Při učení JSON často mate tři čísla: RFC 4627, RFC 7159, RFC 8259 a k tomu ECMA-404. Vztah je přitom celkem jasný:
| Text | Datum | Stav | Klíčový rozdíl |
|---|---|---|---|
| RFC 4627 | 2006 | Nahrazen | Nejvyšší úrovní musí být objekt nebo pole |
| RFC 7159 | 2014 | Nahrazen | Nejvyšší úroveň uvolněna na libovolnou hodnotu JSON |
| RFC 8259 | 2017.12 | Platný standard | Výslovně UTF-8, doplněny pokyny k interoperabilitě |
| ECMA-404 | 2013, druhé vydání 2017.12 | Gramatická specifikace | Definuje pouze gramatiku, neřeší kódování ani přenos |
Který si přečíst? Stačí RFC 8259. ECMA-404 je v gramatické části ekvivalentní; rozdíl je v tom, že RFC 8259 navíc určuje „přenos v UTF-8“ a „problémy interoperability, na které mají implementátoři dbát“.
2. Šest typů hodnot
Gramatické produkce specifikace lze zhuštit do jednoho diagramu:
JSON-text = ws value ws
value = object / array / number / string / "true" / "false" / "null"
object = "{" [ member *( "," member ) ] "}"
member = string ws ":" ws value
array = "[" [ value *( "," value ) ] "]"
Podstatných je jen šest bodů:
- Objekty používají složené závorky, členy se oddělují čárkou a čárka za posledním členem není povolena;
- Pole používají hranaté závorky, prvky se oddělují čárkou a koncové čárky rovněž nejsou povoleny;
- Čísla nepovolují úvodní
+, úvodní nuly (01je neplatné), samotnou desetinnou tečku (1.je neplatné) aniNaNneboInfinity; - Řetězce musí používat dvojité uvozovky; jednoduché uvozovky jsou neplatné;
- Booleany a null jsou literály s malými písmeny;
TrueiNULLjsou neplatné; - Mezi strukturálními prvky smějí být pouze prázdné znaky
0x20(mezera),0x09(tabulátor),0x0A(posun řádku) a0x0D(návrat vozíku).
Následující úryvek je platný JSON, i když mnoho lidí při prvním pohledu předpokládá opak – je to totiž samotný řetězec na nejvyšší úrovni:
"just a string"
Od RFC 7159 může být nejvyšší úrovní skutečně libovolná hodnota. V API se však stále doporučuje používat pouze objekty nebo pole; důvod najdete v části 6.
3. Co může skutečně být přímo uvnitř řetězce
Toto je část, kde nejsnáze klame „u mě to běží bez problému“. Pravidla specifikace pro řetězce jsou:
- Znaky, které smějí být přímo:
%x20-21,%x23-5B,%x5D-10FFFF; - Jinými slovy, dvojitou uvozovku
"(0x22) a zpětné lomítko\(0x5C) je nutné escapovat; - Všechny řídicí znaky pod 0x20 je nutné escapovat, včetně posunu řádku, tabulátoru a návratu vozíku;
- K dispozici jsou pouze escape sekvence
\",\\,\/,\b,\f,\n,\r,\ta\uXXXX.
{
"ok": "Čínský text, emoji 🎉 i celošířkové symboly lze psát přímo",
"escaped": "Sem patří zalomení řádku\na zde končí tabulátor\t",
"control": "\u0007 je zvonek a musí být escapován",
"quote": "Řekl: \" Nepoužívejte jednoduché uvozovky \""
}
\/ je historická escape sekvence (brání tomu, aby </script> uzavřel skript příliš brzy). Moderní scénáře ji potřebují zřídka, ale parsery ji musí podporovat.
4. Kódování: UTF-8, žádný BOM
Část 8.1 RFC 8259 říká tři věci:
- Text JSON určený k výměně dat musí být kódován v UTF-8;
- Implementace nesmějí přidávat BOM (
EF BB BF) na začátek textu; - Parsery smějí BOM ignorovat, aby byly robustnější.
Dvě typické poruchy v praxi:
- Windows Poznámkový blok ukládá jako „UTF-8 s BOM“, soubor pak
JSON.parseodmítne sUnexpected token; - Čínský soubor JSON uložený v GBK se v pipeline, která dekóduje pouze UTF-8, změní v rozsypaný text, přitom syntaxe náhodou projde, a chyba se tak odloží do obchodní vrstvy.
Zpracování je prosté: při čtení souboru odstraňte BOM a poté explicitně dekódujte jako UTF-8; při zápisu zajistěte, aby se BOM nepřidal.
/**
* Odstraní UTF-8 BOM na začátku řetězce.
* @param text Původní text
* @returns Text bez BOM
*/
function stripBom(text: string): string {
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
}
Ještě poznámka k mediálnímu typu: application/json už nepřijímá parametr charset; jeho uvedení nevyvolá chybu, ale jde o zastaralou praxi.
5. Tři místa, kde specifikace záměrně ponechává prostor
Právě tam, kde se specifikace zdráhá něco přesně určit, vznikají rozdíly mezi implementacemi.
1. Rozsah a přesnost čísel
RFC 8259 pouze říká, že „tato specifikace neomezuje rozsah ani přesnost čísel“, a poznamenává, že většina implementací používá dvojitou přesnost IEEE 754.
Znamená to, že stejné 9223372036854775807 se v JavaScriptu stane 9223372036854776000, zatímco v Javě typu long zůstane přesné.
Závěr: celá čísla nad 2^53 - 1 a peněžní částky vyžadující mnoho desetinných míst vždy přenášejte jako řetězce a ve schématu explicitně zapište pattern.
2. Duplicitní klíče
Specifikace říká, že „názvy uvnitř objektu by měly (SHOULD) být jedinečné“; neříká, co dělat, když nejsou.
JSON.parseponechá poslední;- některé parsery ponechají první;
- některé knihovny prostě vyhodí chybu.
Závěr: nepište duplicitní klíče a ve validátorech se nespoléhejte na žádné konkrétní chování. Přidat při sestavení krok „detekce duplicitních klíčů“ je mnohem levnější než pozdější vyšetřování.
3. Limity hloubky a délky
Specifikace nestanoví žádný limit hloubky vnoření, ale každá implementace nějaký má. V8 při desítkách tisíc úrovní vnoření vyhodí RangeError: Maximum call stack size exceeded.
Servery musí zavést vlastní limity; jde o bezpečnostní požadavek, nikoli požadavek specifikace.
6. Ověřte porozumění na protipříkladech
Každý řádek níže je neplatný JSON. Vložte je do parseru a podívejte se, zda jsou chybové zprávy užitečné:
{ 'name': 'json' } // jednoduché uvozovky
{ name: "json" } // název klíče bez uvozovek
{"a": 1,} // koncová čárka
[1, 2, 3,] // koncová čárka v poli
{"a": 01} // úvodní nula
{"a": +1} // úvodní znaménko plus
{"a": .5} // chybějící celá část
{"a": NaN} // ne‑standardní literál
{"a": undefined} // není hodnota JSON
{"a": 1} // comment // komentář
Zajímavý hraniční případ: {"a": 1} následované komentářem je platné v JSON5, platné v JSONC a neplatné ve standardním JSON.
Takže „konfigurační soubor běží v pořádku, ale API ho odmítne“ je téměř vždy problém dialektu.
7. Produkční kontrolní seznam
- [ ] Veškerý JSON přenášený ven používá
application/jsonbezcharset; - [ ] Pipeline pro čtení i zápis souborů odstraňuje BOM a používá explicitní UTF-8;
- [ ] Celá čísla nad
2^53jsou přenášena jako řetězce a označena ve schématu; - [ ] Duplicitní klíče jsou detekovány při sestavení;
- [ ] Server omezuje velikost těla požadavku a hloubku vnoření;
- [ ] Hodnoty na nejvyšší úrovni veřejných API jsou objekty nebo pole, nikdy samostatné skaláry.
Časté dotazy
Jak rychle zjistit, zda je text platný JSON?
Předložte text parseru nebo online nástroji pro parsování, přečtěte pozici prvního neplatného znaku a zkontrolujte ji proti šesti typům hodnot a pravidlům pro prázdné znaky z RFC 8259. Nejvyšší úrovní může být libovolná hodnota JSON, ale mezi strukturálními prvky smějí být pouze mezera, tabulátor, posun řádku a návrat vozíku. Jednoduchá uvozovka, klíč bez uvozovek, koncová čárka nebo komentář znamená, že nejde o standardní JSON.
Proč musí JSON používat UTF-8 a nesmí obsahovat BOM?
Část 8.1 RFC 8259 vyžaduje, aby text JSON určený k výměně dat byl kódován v UTF-8, říká, že implementace nesmějí přidávat BOM na začátek, a umožňuje parserům BOM kvůli robustnosti ignorovat. Soubor s BOM JSON.parse odmítne s Unexpected token, i když na pohled nevypadá nic špatně. Při čtení tedy BOM odstraňte a dekódujte explicitně jako UTF-8.
Mám použít JSON5 nebo JSONC, abych tato gramatická omezení obešel?
Ano, ale pouze pro místní čtení, například konfiguračních souborů; data vyměňovaná mezi systémy by měla zůstat standardním JSON. Protipříklady ukazují, že {"a": 1} následované komentářem je platné v JSON5 a JSONC, ale neplatné ve standardním JSON, a proto je „konfigurační soubor běží, ale API ho odmítne“ téměř vždy problém dialektu. Při převodu nejprve odstraňte komentáře, jednoduché uvozovky a koncové čárky a výsledek předejte standardnímu parseru.