JSON Toolkit

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ę.

Początkujący~11 min czytaniaOpublikowano 2026-10-30Zaktualizowano 2026-10-30
Okładka artykułu o granicach gramatyki JSON w RFC 8259

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:

  1. Obiekty używają nawiasów klamrowych, składowe są rozdzielane przecinkami, a przecinek po ostatniej składowej jest niedozwolony;
  2. Tablicy używają nawiasów kwadratowych, elementy są rozdzielane przecinkami, a przecinki końcowe również są niedozwolone;
  3. Liczby nie dopuszczają wiodącego +, wiodących zer (01 jest niepoprawne), samej kropki dziesiętnej (1. jest niepoprawne) ani NaN lub Infinity;
  4. Łańcuchy muszą używać podwójnych cudzysłowów; pojedyncze cudzysłowy są niepoprawne;
  5. Wartości logiczne i null to literały pisane małymi literami; True i NULL są niepoprawne;
  6. Między elementami struktury mogą występować wyłącznie białe znaki 0x20 (spacja), 0x09 (tabulator), 0x0A (przesunięcie wiersza) i 0x0D (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, \t i \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:

  1. Tekst JSON używany do wymiany musi być zakodowany w UTF-8;
  2. Implementacje nie mogą dodawać BOM (EF BB BF) na początku tekstu;
  3. 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.parse z komunikatem Unexpected 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.parse zachowuje 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/json bez charset;
  • [ ] Potoki odczytu i zapisu plików usuwają BOM i używają jawnego UTF-8;
  • [ ] Liczby całkowite powyżej 2^53 są 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.