Granice gramatyki JSON w świetle RFC 8259
Podsumowanie: Przekład reguł gramatycznych RFC 8259 na prosty język: które znaki mogą występować bezpośrednio, które trzeba poprzedzić ucieczką i gdzie specyfikacja celowo pozostawia swobodę.

Najważniejsze: gdy pojawia się błąd składni JSON, znajdź pierwszy nielegalny znak i zaklasyfikuj go jako pojedynczy cudzysłów, przecinek końcowy, komentarz lub znak sterujący, zamiast czytać cały dokument od nowa; po stronie kodowania potwierdź UTF-8 bez BOM, a liczby całkowite powyżej 2^53 - 1 przenoś jako łańcuchy.
Treść specyfikacji liczy zaledwie kilkanaście stron, ale za każdym „MUST”, „SHOULD” i „MAY” kryje się incydent, który kiedyś nie pozwolił zespołowi spać przez całą noc.
1. Jak odnoszą się do siebie trzy teksty
Nauka JSON bywa myląca przez trzy numery: RFC 4627, RFC 7159, RFC 8259 oraz ECMA-404. Relacja jest jednak całkiem jasna:
| Tekst | Data | Status | Kluczowa różnica |
|---|---|---|---|
| RFC 4627 | 2006 | Zastąpiony | Wierzchołkiem musi być obiekt lub tablica |
| RFC 7159 | 2014 | Zastąpiony | Wierzchołek złagodzony do dowolnej wartości JSON |
| RFC 8259 | 2017.12 | Aktualny standard | Wyraźny UTF-8, dodane wytyczne interoperacyjności |
| ECMA-404 | 2013, drugie wydanie 2017.12 | Specyfikacja gramatyki | Definiuje tylko gramatykę, nie obejmuje kodowania ani transportu |
Który czytać? Wystarczy sam RFC 8259. ECMA-404 jest równoważny w części gramatycznej; różnica polega na tym, że RFC 8259 określa także „transport jako UTF-8” oraz „problemy interoperacyjności, na które implementatorzy powinni uważać”.
2. Sześć typów wartości
Reguły gramatyczne specyfikacji można streścić w jednym schemacie:
JSON-text = ws value ws
value = object / array / number / string / "true" / "false" / "null"
object = "{" [ member *( "," member ) ] "}"
member = string ws ":" ws value
array = "[" [ value *( "," value ) ] "]"
Wystarczy zapamiętać sześć punktów:
- Obiekty używają nawiasów klamrowych, składowe są rozdzielane przecinkami, a przecinek po ostatniej składowej jest niedozwolony;
- Tablicy używają nawiasów kwadratowych, elementy są rozdzielane przecinkami, a przecinki końcowe również są niedozwolone;
- Liczby nie dopuszczają wiodącego
+, wiodących zer (01jest niepoprawne), samej kropki dziesiętnej (1.jest niepoprawne) aniNaNlubInfinity; - Łańcuchy muszą używać podwójnych cudzysłowów; pojedyncze cudzysłowy są niepoprawne;
- Wartości logiczne i null to literały pisane małymi literami;
TrueiNULLsą niepoprawne; - Między elementami struktury mogą występować wyłącznie białe znaki
0x20(spacja),0x09(tabulator),0x0A(przesunięcie wiersza) i0x0D(powrót karetki).
Poniższy fragment jest poprawnym JSON-em, choć wiele osób przy pierwszym kontakcie uzna go za błędny — bo to samodzielny łańcuch wierzchołkowy:
"just a string"
Od RFC 7159 wierzchołkiem rzeczywiście może być dowolna wartość. W API nadal zaleca się jednak używać tylko obiektów lub tablic; powód znajdziesz w sekcji 6.
3. Co naprawdę może wystąpić bezpośrednio w łańcuchu
To część, w której najłatwiej dać się zwieść stwierdzeniu „u mnie działa”. Reguły specyfikacji dla łańcuchów są następujące:
- Znaki dozwolone bezpośrednio:
%x20-21,%x23-5B,%x5D-10FFFF; - Innymi słowy, cudzysłów
"(0x22) i backslash\(0x5C) muszą być poprzedzone ucieczką; - Wszystkie znaki sterujące poniżej 0x20 muszą być poprzedzone ucieczką, w tym przesunięcie wiersza, tabulator i powrót karetki;
- Dostępne sekwencje ucieczki to wyłącznie
\",\\,\/,\b,\f,\n,\r,\ti\uXXXX.
{
"ok": "Chinese text, emoji 🎉 and full-width symbols can be written directly",
"escaped": "A line break goes here\nand a tab\tfinishes it",
"control": "\u0007 is the bell character and must be escaped",
"quote": "He said: \"don't use single quotes\""
}
\/ to ucieczka odziedziczona (zapobiega wczesnemu zamknięciu </script>). We współczesnych scenariuszach rzadko potrzebna, ale parsery muszą ją obsługiwać.
4. Kodowanie: UTF-8, bez BOM
Sekcja 8.1 RFC 8259 mówi trzy rzeczy:
- Tekst JSON używany do wymiany musi być zakodowany w UTF-8;
- Implementacje nie mogą dodawać BOM (
EF BB BF) na początku tekstu; - Parsery mogą ignorować BOM, aby być bardziej odporne.
Dwie typowe awarie w realnym świecie:
- Notatnik w Windows zapisuje jako „UTF-8 z BOM”, a plik zostaje odrzucony przez
JSON.parsez komunikatemUnexpected token; - Chiński plik JSON zapisany w GBK zamienia się w krzaczki w potoku, który dekoduje tylko jako UTF-8, ale składnia przypadkiem nadal przechodzi, więc błąd ujawnia się dopiero w warstwie biznesowej.
Obsługa jest prosta: przy odczycie usuń BOM, następnie zdekoduj jawnie jako UTF-8; przy zapisie upewnij się, że BOM nie jest dodawany.
/**
* Removes the UTF-8 BOM at the start of a string.
* @param text The original text
* @returns The text without a BOM
*/
function stripBom(text: string): string {
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
}
Skoro mowa o typach nośnych: application/json nie akceptuje już parametru charset; jego dopisanie nie wywołuje błędu, ale jest przestarzałą praktyką.
5. Trzy miejsca, w których specyfikacja celowo pozostawia swobodę
Właśnie w miejscach, w których specyfikacja odmawia ustalenia szczegółów, powstają różnice między implementacjami.
1. Zakres i precyzja liczb
RFC 8259 mówi tylko, że „specyfikacja nie ogranicza zakresu ani precyzji liczb”, zaznaczając, że większość implementacji używa podwójnej precyzji IEEE 754.
Oznacza to, że ta sama liczba 9223372036854775807 w JavaScript staje się 9223372036854776000, a w Javie jako long pozostaje dokładna.
Wniosek: liczby całkowite powyżej 2^53 - 1 oraz kwoty pieniężne wymagające wielu miejsc dziesiętnych powinny zawsze być przenoszone jako łańcuchy, a pattern należy jawnie zapisać w schemacie.
2. Zduplikowane klucze
Sformułowanie specyfikacji brzmi: „nazwy w obrębie obiektu powinny (SHOULD) być unikatowe”; nie mówi, co robić, gdy nie są.
JSON.parsezachowuje ostatni;- Niektóre parsery zachowują pierwszy;
- Niektóre biblioteki po prostu zgłaszają błąd.
Wniosek: nie zapisuj zduplikowanych kluczy i nie polegaj na konkretnym zachowaniu walidatorów. Dodanie etapu „wykrywania zduplikowanych kluczy” w czasie budowania jest znacznie tańsze niż późniejsze śledztwo.
3. Limity głębokości i długości
Specyfikacja nie ustala limitu głębokości zagnieżdżenia, ale każda implementacja go ma. V8 zgłasza RangeError: Maximum call stack size exceeded przy dziesiątkach tysięcy poziomów zagnieżdżenia.
Serwery muszą narzucać własne limity; to wymóg bezpieczeństwa, a nie wymóg specyfikacji.
6. Sprawdzenie zrozumienia za pomocą kontrprzykładów
Każda poniższa linia to niepoprawny JSON. Podaj je parserowi i sprawdź, czy komunikaty błędów są pomocne:
{ 'name': 'json' } // single quotes
{ name: "json" } // key name not quoted
{"a": 1,} // trailing comma
[1, 2, 3,] // trailing comma in an array
{"a": 01} // leading zero
{"a": +1} // leading plus sign
{"a": .5} // missing integer part
{"a": NaN} // non-standard literal
{"a": undefined} // not a JSON value
{"a": 1} // comment // comment
Ciekawy przypadek brzegowy: {"a": 1} z komentarzem jest poprawny w JSON5, poprawny w JSONC i niepoprawny w standardowym JSON-ie.
Dlatego „plik konfiguracyjny działa, ale API go odrzuca” prawie zawsze oznacza problem z dialektem.
7. Lista kontrolna dla produkcji
- [ ] Cały JSON wysyłany na zewnątrz używa
application/jsonbezcharset; - [ ] Potoki odczytu i zapisu plików usuwają BOM i używają jawnego UTF-8;
- [ ] Liczby całkowite powyżej
2^53są przenoszone jako łańcuchy i opisane w schemacie; - [ ] Zduplikowane klucze są wykrywane w czasie budowania;
- [ ] Serwer ogranicza rozmiar treści żądania i głębokość zagnieżdżenia;
- [ ] Wierzchołkowe wartości publicznych API to obiekty lub tablice, nigdy samodzielne skalary.
FAQ
Jak szybko sprawdzić, czy dany tekst jest poprawnym JSON-em?
Oddaj tekst parserowi lub narzędziu do parsowania online, odczytaj położenie pierwszego nielegalnego znaku, a następnie porównaj je z sześcioma typami wartości i regułami białych znaków z RFC 8259. Wierzchołkiem może być dowolna wartość JSON, ale między elementami struktury dozwolone są tylko spacja, tabulator, przesunięcie wiersza i powrót karetki. Pojedynczy cudzysłów, klucz bez cudzysłowów, przecinek końcowy lub komentarz oznaczają, że to nie jest standardowy JSON.
Dlaczego JSON musi używać UTF-8 i nie może zawierać BOM?
Sekcja 8.1 RFC 8259 wymaga, aby tekst JSON używany do wymiany był zakodowany w UTF-8, mówi, że implementacje nie mogą dodawać BOM na początku, i pozwala parserom ignorować BOM dla odporności. Plik z BOM zostaje odrzucony przez JSON.parse z komunikatem Unexpected token, choć dla oka nic nie wygląda źle. Dlatego przy odczycie usuń BOM i dekoduj jawnie jako UTF-8.
Czy użyć JSON5 lub JSONC, aby obejść te ograniczenia gramatyki?
Tak, ale tylko do lokalnego odczytu, takiego jak pliki konfiguracyjne; dane wymieniane między systemami nadal powinny być standardowym JSON-em. Kontrprzykłady pokazują, że {"a": 1} z komentarzem jest poprawny w JSON5 i JSONC, ale niepoprawny w standardowym JSON-ie, dlatego „plik konfiguracyjny działa, ale API go odrzuca” prawie zawsze oznacza problem z dialektem. Podczas konwersji najpierw usuń komentarze, pojedyncze cudzysłowy i przecinki końcowe, a wynik podaj standardowemu parserowi.