JSON Toolkit

FAQ dotyczące składni i formatów: komentarze, przecinki końcowe i precyzja

Podsumowanie: Czy komentarze są dozwolone, dlaczego przecinek końcowy daje błąd, czy klucze wymagają cudzysłowów, dlaczego 9007199254740993 się zmienia — dwanaście wniosków, każdy sprawdzony w świetle RFC 8259.

Początkujący~12 min czytaniaOpublikowano 2026-10-30Zaktualizowano 2026-10-30
Okładka FAQ dotyczącego składni i formatów

Najważniejsze: pisz zwykły JSON zgodny z RFC 8259 — podwójne cudzysłowy, brak komentarzy, brak przecinków końcowych, UTF-8 bez BOM — dla wszystkiego, co przekracza granicę procesu; używaj JSONC lub JSON5 do konfiguracji wymagającej komentarzy i konwertuj z powrotem w czasie wykonania; a liczby całkowite powyżej 2^53-1 serializuj po stronie serwera jako łańcuchy.

Co te pytania łączy: wyglądają, jakby „powinny działać”, ale specyfikacja jest naprawdę bardzo ścisła.

1. Co uchodzi za poprawny JSON

Jaka jest różnica między JSON, JSON5 i JSONC?

Standardowy JSON rozpoznaje tylko łańcuchy w podwójnych cudzysłowach i nie pozwala ani na komentarze, ani na przecinki końcowe. JSON5 i JSONC dodają funkcje na jego wierzchu:

Funkcja JSON (RFC 8259) JSONC JSON5
Łańcuchy w podwójnych cudzysłowach Wspierane Wspierane Wspierane
Łańcuchy w pojedynczych cudzysłowach Niewspierane Niewspierane Wspierane
Komentarze Niewspierane Wspierane Wspierane
Przecinki końcowe Niewspierane Niewspierane Wspierane
Klucze bez cudzysłowów Niewspierane Niewspierane Wspierane
Liczby szesnastkowe Niewspierane Niewspierane Wspierane
NaN / Infinity Niewspierane Niewspierane Wspierane
Rozszerzenie pliku .json .jsonc .json5

Zasada wyboru jest prosta: zawsze używaj standardowego JSON-a przekraczając granice procesów, a pliki konfiguracyjne mogą używać JSONC lub JSON5 w zależności od tego, co wspiera twój edytor. tsconfig.json i .vscode/settings.json mogą zawierać komentarze, bo edytor parsuje je jako JSONC; podanie ich do ścisłego JSON.parse nadal daje błąd.

Czy JSON może zawierać komentarze?

Nie. Gramatyka RFC 8259 nie ma produkcji komentarza, więc dowolne // lub /* */ psuje parsowanie.

Jeśli chcesz zachować wyjaśnienia w konfiguracji, istnieją trzy alternatywy:

{
  "_comment": "以下超时单位是毫秒",
  "timeout": 3000,
  "retry": {
    "$doc": "重试次数,0 表示不重试",
    "count": 3
  }
}
  • Użyj konwencjonalnych pól takich jak _comment, które czytelnik po prostu ignoruje;
  • Użyj pól z prefiksem $, aby zmniejszyć ryzyko kolizji z polami biznesowymi;
  • Po prostu przejdź na JSONC lub YAML dla konfiguracji i konwertuj do standardowego JSON-a w czasie wykonania.

Dlaczego przecinek końcowy powoduje błąd?

Bo w gramatyce zarówno tablice, jak i obiekty są strukturami „element + separator”, a po ostatnim elemencie przecinek jest niedozwolony.

{
  "a": 1,
  "b": 2,
}

Powyższy fragment ma zbędny przecinek przed }, a błąd jest zwykle zgłaszany przy przecinku lub przy zamykającym nawiasie, w zależności od implementacji parsera. To też najłatwiejsza pułapka przy migracji z JavaScript — literały obiektowe JS pozwalają na przecinki końcowe, JSON nie.

Czy pojedyncze cudzysłowy są dozwolone?

Nie. Łańcuchy muszą używać podwójnych cudzysłowów; 'text' to błąd składni.

Jeśli dane skopiowano z repr() w Pythonie, skryptu powłoki lub ręcznie pisanej konfiguracji, najpierw przekonwertuj je narzędziem, zamiast liczyć na łagodność parsera.

Czy klucze mogą być bez cudzysłowów?

Nie. Nazwy składowych obiektu muszą być łańcuchami ujętymi w podwójne cudzysłowy.

{ name: "json" }

To literał obiektowy JavaScript, a nie JSON. Dodanie cudzysłowów czyni go poprawnym:

{ "name": "json" }

Czy wierzchołkiem może być tablica lub łańcuch?

Tak. RFC 8259 pozwala na dowolną wartość JSON na wierzchołku: obiekty, tablice, łańcuchy, liczby, true, false i null są wszystkie poprawne.

W praktyce inżynieryjnej zaleca się jednak używanie tylko obiektów lub tablic na wierzchołku z dwóch powodów:

  1. Zgodność: starsze parsery (z czasów RFC 4627) wymagają, by wierzchołkiem był obiekt lub tablica;
  2. Bezpieczeństwo: tablica wierzchołkowa historycznie niosła ryzyko JSON hijacking. Współczesne przeglądarki to naprawiły, ale starsze systemy nadal zakładają konwencję.

Więc "hello" jest poprawnym JSON-em i ["a", "b"] też, ale API zwracające {"data": [...]} jest bezpieczniejsze.

2. Kodowanie i łańcuchy

Czy BOM na początku pliku psuje parsowanie?

Tak. RFC 8259 wyraźnie wymaga, by tekst JSON był przesyłany jako UTF-8 i nie może dodawać byte order mark (BOM).

BOM to trzy bajty EF BB BF na samym początku pliku. Jest całkowicie niewidoczny, ale zajmuje pierwszą pozycję łańcucha, więc parser dochodzi do wniosku, że „pierwszy znak to nie {”, i daje błąd — a zgłaszany wiersz i kolumna to zwykle wiersz 1, kolumna 1, co wygląda zdumiewająco.

„Zapisz jako UTF-8” w Notatniku Windows domyślnie zawiera BOM. Aby go sprawdzić i usunąć:

# Check: output starting with efbbbf means a BOM is present
head -c 3 data.json | xxd

# Remove: skip the first 3 bytes with tail
tail -c +4 data.json > clean.json

Współczesny JSON.parse w JavaScript toleruje BOM (V8 pomija go wewnętrznie), ale ścisłe implementacje w Javie, Go i Pythonie nie, co jest częstym powodem, dla którego „ten sam plik daje różne wyniki w różnych językach”.

Czy chińskie znaki trzeba konwertować do \uXXXX?

Nie. RFC 8259 wymaga, by tekst JSON był UTF-8, więc chiński może występować bezpośrednio; {"name": "张三"} jest całkowicie poprawne.

\uXXXX to jedynie równoważna alternatywna pisownia, używana dla kanałów, które nie potrafią przedstawić znaku bezpośrednio. Zasada praktyczna:

  • Treści żądań HTTP, pliki, bazy danych: pisz chiński bezpośrednio, oszczędzając 3 bajty per znak;
  • Nagłówki HTTP, parametry URL, osadzanie w innym łańcuchu: używaj ucieczek, aby uniknąć problemów z kodowaniem.

Jedynymi znakami, które muszą być escape'owane, są znaki sterujące (U+0000 do U+001F). Na przykład nową linię trzeba zapisać jako \n; nie można umieścić literalnej nowej linii wewnątrz łańcucha.

Czy emoji mogą trafić bezpośrednio do łańcucha?

Tak, ale pamiętaj, że zajmuje dwie jednostki kodowe UTF-16.

Emoji i wiele rzadkich znaków (takich jak 𠮷) leżą poza BMP, więc w JavaScript "😀".length === 2. Walidacja po liczbie znaków jest więc łatwa do zepsucia:

const text = "😀";
console.log(text.length);          // 2, number of UTF-16 code units
console.log([...text].length);     // 1, number of code points
console.log(Buffer.byteLength(text)); // 4, number of UTF-8 bytes

Jeśli limit długości pseudonimu jest liczony przez .length, jedna emoji zużywa dwa sloty; jeśli backend liczy bajty, to cztery. Przy wyrównywaniu definicji jasno określ, czy chodzi o punkty kodowe, jednostki kodowe czy bajty.

3. Liczby i wartości specjalne

Dlaczego duża liczba całkowita 9007199254740993 stała się 9007199254740992?

Bo JSON.parse w JavaScript konwertuje liczby do zmiennoprzecinkowych podwójnej precyzji IEEE 754, a największa bezpieczna liczba całkowita to 2^53-1.

Number.MAX_SAFE_INTEGER === 9007199254740991  // true
9007199254740993                              // actually yields 9007199254740992

Każda liczba powyżej 2^53-1 może zostać zaokrąglona w ostatnich cyfrach. Typowymi ofiarami są 19-cyfrowe identyfikatory użytkowników generowane algorytmami snowflake, numery zamówień i znaczniki czasu w nanosekundach.

Trzy poprawki, uporządkowane według preferencji:

  1. Serializuj identyfikator jako łańcuch po stronie serwera: "id": "9007199254740993", usuwając problem precyzji u źródła;
  2. Użyj bezstratnej biblioteki parsującej: zamienniki JSON.parse potrafią zachować duże liczby całkowite dosłownie, na przykład json-bigint;
  3. Wyciągnij wyrażeniem regularnym tylko do wyświetlenia: nie parsuj całego dokumentu, dopasuj tylko docelowe pole jako tekst.

Zauważ, że JSON.stringify ma symetryczny problem: liczba poza bezpiecznym zakresem całkowitoliczbowym może po serializacji nie trzymać już oryginalnej wartości.

Czy NaN i Infinity mogą trafić do JSON?

Nie. Produkcja liczby JSON pozwala tylko na notację dziesiętną, a NaN, Infinity i -Infinity nie są poprawnymi wartościami JSON.

Podejście JavaScript to „cicha degradacja”:

JSON.stringify({ value: NaN });        // '{"value":null}'
JSON.stringify({ value: Infinity });   // '{"value":null}'

Żadnego błędu, NaN po prostu staje się null. To klasyczna scena, w której „frontend wysłał NaN, a backend odebrał null”. Gdy musisz przekazać taką semantykę, wyraź ją jawnie łańcuchami lub uzgodnionymi wartościami wartownikowymi:

{ "ratio": null, "ratioState": "not-a-number" }

Czy liczby można zapisać jako 1e3 lub 0.5?

Tak. Liczby JSON wspierają ułamki i wykładniki, więc 1e3, 1E3, 0.5 i -0.5 są wszystkie poprawne.

Ale istnieją ograniczenia:

  • Nie mogą zaczynać się ani kończyć kropką: .5 i 5. są niepoprawne, więc zapisz 0.5 i 5;
  • Brak zer wiodących: 01 jest niepoprawne;
  • Brak szesnastkowych: 0x1F jest niepoprawne, to rozszerzenie JSON5;
  • -0 jest poprawne, ale JSON.stringify(-0) wypisuje 0, tracąc znak.

4. Semantyka obiektów

Co się dzieje przy zduplikowanych kluczach?

Zachowanie zależy od implementacji; RFC 8259 mówi tylko, że nazwy kluczy „powinny” być unikatowe, i nie określa, co dzieje się przy duplikatach.

W praktyce zdecydowana większość implementacji bierze ostatnią wartość:

JSON.parse('{"a":1,"a":2}')  // { a: 2 }

Ale nie możesz na tym polegać. Niektóre biblioteki Java, encoding/json w Go i json w Pythonie różnią się między wersjami: część bierze pierwszą wartość, część zgłasza błąd, a część ostatnią. Traktowanie zduplikowanych kluczy jako błędu jest najbezpieczniejszym podejściem między językami.

Nie znajdziesz zduplikowanych kluczy wzrokiem — w zminifikowanym JSON-ie ten sam klucz może być oddalony o tysiące znaków. Użyj skryptu:

# Extract keys that appear more than once at the same level, as a rough check
jq -r 'paths(scalars) | join(".")' data.json | sort | uniq -d

W co zamienia się undefined?

To zależy, i jest to najbardziej podatne na błędy miejsce:

JSON.stringify({ a: undefined });              // '{}'  the key is dropped entirely
JSON.stringify({ a: undefined, b: 1 });        // '{"b":1}'
JSON.stringify([undefined]);                   // '[null]'  becomes null inside arrays
JSON.stringify({ a: function () {} });         // '{}'  functions are dropped too

To samo undefined znaczy „pole znika” wewnątrz obiektu i „staje się null” wewnątrz tablicy. Jeśli backend bierze różne gałęzie dla „brak pola” i „pole jest null”, dostajesz tu niespójność.

Gdy musisz jawnie wyrazić „brak wartości”, użyj null, a nie undefined.

Co się dzieje po sparsowaniu {"__proto__": {...}}?

Sam JSON.parse jest bezpieczny: __proto__ jest traktowane jako zwykła właściwość własna i nie zmienia prototypu.

Niebezpieczna jest operacja scalania po parsowaniu:

const patch = JSON.parse('{"__proto__": {"isAdmin": true}}');
Object.assign({}, patch);       // some implementations pollute the prototype

Właściwym podejściem jest iterowanie właściwości własnych i pomijanie __proto__, constructor i prototype. Szczegóły w FAQ dotyczącym wydajności i bezpieczeństwa.

5. Szybka ściągawka

Składnia Poprawne Uwagi
{"a": 1,} Nie Przecinek końcowy
{'a': 1} Nie Pojedyncze cudzysłowy
{a: 1} Nie Klucz bez cudzysłowów
{"a": 1} // comment Nie Komentarze
"hello" Tak Łańcuch wierzchołkowy jest poprawny, ale rzadko używany w API
{"a": 1e3} Tak Wykładniki są wspierane
{"a": 01} Nie Zero wiodące
{"a": NaN} Nie Niepoprawny literał; stringify zamienia go w null
\uFEFF{"a":1} Nie BOM nie jest częścią tekstu JSON
{"a":1,"a":2} Sporne Implementacje biorą ostatnią wartość, ale nie polegaj na tym