JSON5 i JSONC: JSON z komentarzami
Podsumowanie: Oba dialekty istnieją, bo konfiguracja pisana przez ludzi wymaga komentarzy i przecinków końcowych, ale ich składnia i zasięg ekosystemu się różnią, więc zły wybór zakleszcza łańcuch narzędzi.

Najważniejsze: używaj JSONC do komentarzy w konfiguracji VS Code i TypeScript, używaj JSON5, gdy potrzebujesz szerszej składni literałów ES5.1, takiej jak pojedyncze cudzysłowy lub klucze bez cudzysłowów, a publiczne API pozostawiaj na standardowym JSON-ie.
Standardowy JSON nie pozwala na komentarze, więc każdy zespół po cichu i tak je pisze — aż pewnego dnia budowanie się psuje. Zamiast robić to potajemnie, wybierz właściwy dialekt.
1. Dwa dialekty mają różne motywacje
- JSONC (JSON with Comments): standardowy JSON plus komentarze i przecinki końcowe, bez prawie żadnych innych zmian składni. To de facto standard ekosystemu VS Code —
settings.json,tsconfig.jsoni.eslintrc.jsonużywają go. - JSON5: znacznie ambitniejszy, wyrównany bezpośrednio do literału obiektowego ES5.1 — pojedyncze cudzysłowy, klucze bez cudzysłowów, szesnastkowe,
Infinity/NaN, kontynuacja wiersza backslashem i wiodące znaki+.
Jedno zdanie, aby je rozróżnić: JSONC tylko czyni pisanie wygodnym; JSON5 pozwala pisać plik konfiguracyjny tak, jakby był JavaScriptem.
2. Tabela porównania składni
| Cecha | Standardowy JSON | JSONC | JSON5 |
|---|---|---|---|
Komentarze // /* */ |
✗ | ✓ | ✓ |
| Przecinki końcowe | ✗ | ✓ | ✓ |
| Łańcuchy w pojedynczych cudzysłowach | ✗ | ✗ | ✓ |
| Klucze bez cudzysłowów | ✗ | ✗ | ✓ |
| Liczby szesnastkowe | ✗ | ✗ | ✓ |
Wiodąca/końcowa kropka dziesiętna .5 5. |
✗ | ✗ | ✓ |
Liczby zaczynające się od + |
✗ | ✗ | ✓ |
Infinity / NaN |
✗ | ✗ | ✓ |
Kontynuacja \ na końcu wiersza |
✗ | ✗ | ✓ |
| Łańcuchy wieloliniowe | ✗ | ✗ | ✓ (zrealizowane przez kontynuację) |
| Dowolna wartość wierzchołkowa | ✓ | ✓ | ✓ |
Pełna specyfikacja JSON5 liczy ledwie więcej niż stronę; pakiet json5 jest od dawna utrzymywany przez społeczność, a przeglądarki i Node zachowują się identycznie.
3. Parsowanie: dwa API, dwa rodzaje możliwości lokalizacji
3.1 JSON5: proste parsowanie całego dokumentu
import JSON5 from 'json5';
const config = JSON5.parse(`
{
// service port
port: 8080,
host: '0.0.0.0',
features: ['json5', 'jsonc',],
retries: 0x10,
}
`);
console.log(config.port, config.retries); // 8080 16
JSON5.parse zwraca zwykły obiekt bez informacji o przesunięciach — bo nie jest nastawione na scenariusze edycji, tylko na „czytanie konfiguracji”.
3.2 JSONC: drzewo składni z przesunięciami
Wartość jsonc-parser nie leży w parsowaniu, ale w edycji. Oferuje trzy warstwy API:
| API | Cel | Typowe użycie |
|---|---|---|
parse |
Parsowanie do drzewa niosącego offset/length |
Podświetlanie składni, zwijanie, nawigacja |
visit |
Przejście w głąb; callback otrzymuje ścieżkę węzła | Inspekcja w stylu JSONPath |
modify + applyEdits |
Wytworzenie minimalnej edycji tekstu, a następnie zastosowanie jej do oryginału | Zmiana wartości z zachowaniem komentarzy |
import { parseTree, findNodeAtLocation, modify, applyEdits } from 'jsonc-parser';
const text = `{
// port configuration
"server": {
"port": 8080 // 80 in production
}
}`;
const tree = parseTree(text);
const portNode = findNodeAtLocation(tree!, ['server', 'port']);
console.log(portNode?.offset, portNode?.length);
const edits = modify(text, ['server', 'port'], 3000, {
formattingOptions: { insertSpaces: true, tabSize: 2 },
});
console.log(applyEdits(text, edits));
Wyjście zachowuje oryginalne komentarze i zastępuje tylko trzy znaki 8080:
{
// port configuration
"server": {
"port": 3000 // 80 in production
}
}
Właśnie tego nie potrafi „edycja konfiguracji wyrażeniami regularnymi”: regex nie wie, który 8080 zmienić, i nie wie, że 8080 wewnątrz łańcucha trzeba zostawić.
3.3 Komunikaty błędów muszą nieść przesunięcia
Kody błędów z jsonc-parser to wartości wyliczeniowe; najczęstsze to:
| Kod błędu | Znaczenie |
|---|---|
InvalidSymbol |
Pojawił się znak, którego nie powinno być |
PropertyNameExpected |
Oczekiwano nazwy klucza |
ColonExpected |
Oczekiwano dwukropka |
CommaExpected |
Oczekiwano przecinka |
CloseBraceExpected |
Obiekt nie został zamknięty |
UnexpectedEndOfComment |
Komentarz nie został zamknięty |
InvalidEscapeCharacter |
Niepoprawna ucieczka |
Gdy offset zostanie przetłumaczone na wiersz i kolumnę, edytor potrafi narysować falę dokładnie w miejscu błędu.
4. Kiedy czego użyć
| Scenariusz | Zalecenie | Powód |
|---|---|---|
| Treści żądań i odpowiedzi publicznych API | Standardowy JSON | Dialekty nie są wspierane przez uniwersalnych klientów |
package.json, composer.json |
Standardowy JSON | Menedżery pakietów rozumieją tylko standardowy JSON |
tsconfig.json, .vscode/settings.json |
JSONC | Domyślny wybór ekosystemu, natywnie wspierany przez łańcuch narzędzi |
| Konfiguracja aplikacji (wymaga komentarzy) | JSONC lub YAML | Komentarze muszą przetrwać, a narzędzia nadal muszą czytać i zapisywać |
Potrzeba Infinity, szesnastkowych, złożonych literałów |
JSON5 | Największa siła wyrazu |
| Wymiana danych, logi, cache | Format liniowy lub standardowy JSON | Wymagane strumieniowanie i uniwersalność |
Zasada praktyczna: jeśli plik będzie czytany przez „narzędzie nieludzkie”, preferuj standardowy JSON; rozważ dialekt tylko wtedy, gdy masz pewność, że czyta go wyłącznie twój własny kod.
5. Trzy rodzaje ryzyka, które wprowadzają dialekty
5.1 Rozlanie się wymagania
Gdy inne narzędzie czyta plik konfiguracyjny JSON5 i wywołuje JSON.parse, natychmiast się wysypuje. Typowy objaw to „działa lokalnie, zawodzi na Dockerze” — bo kontener uruchamia inną ścieżkę odczytu.
Środek zaradczy: dodaj do repozytorium kontrolę CI, która jawnie wymienia pliki mogące używać dialektu; wszystko inne jest walidowane jako standardowy JSON.
5.2 Niespodzianki wokół wnioskowania typów
JSON5 pozwala na klucze bez cudzysłowów i pojedyncze cudzysłowy, ale nie pozwala wnosić literałów takich jak daty czy wyrażenia regularne — to nadal format danych, a nie ewaluacja JavaScript.
Ludzie zakładali, że JSON5 potrafi wyrazić new Date(), by zobaczyć, że parsowanie zawodzi. JSON5 to nie eval.
5.3 Komentarze po cichu odrzucane
Ani JSON5.stringify, ani JSON.stringify nie zachowuje komentarzy. Tak więc „wczytaj, popraw wartość, zapisz z powrotem” gubi każdy komentarz.
Gdy komentarze muszą przetrwać, jedyne opcje to API edycji jsonc-parser albo biblioteka YAML wspierająca zachowanie komentarzy.
6. Praktyczne zalecenia
- [ ] Trzymaj w dokumentacji zespołu tabelę „typ pliku → format”, aby nikt nie improwizował;
- [ ] Owiń odczyt konfiguracji w jedną funkcję
loadConfig(), która wewnętrznie decyduje, którego parsera użyć, aby kod biznesowy nigdy nie wywoływałJSON.parsebezpośrednio; - [ ] Przy programowej modyfikacji konfiguracji z komentarzami używaj
modify+applyEdits; nigdy nie przepisuj całego pliku; - [ ] Waliduj pliki dialektu również w CI (
jsonc-parserdla JSONC,JSON5.parsedla JSON5), inaczej błędy składni ujawniają się dopiero w czasie wykonania; - [ ] Używaj standardowego JSON-a dla publicznych interfejsów, kluczy cache i ładunków kolejek komunikatów.
FAQ
Jak wybrać między JSON5 i JSONC?
Wybieraj według środowiska wykonania, a nie liczby konstrukcji składni: VS Code, TypeScript i ESLint domyślnie czytają JSONC, więc JSONC daje najlepszą zgodność; po JSON5 sięgaj tylko wtedy, gdy potrzebujesz szerszej składni literałów ES5.1, takiej jak pojedyncze cudzysłowy, klucze bez cudzysłowów lub Infinity. Żaden dialekt nie nadaje się do publicznych odpowiedzi API.
Dlaczego standardowy parser JSON nie potrafi czytać JSONC bezpośrednio?
JSONC pozwala na komentarze i przecinki końcowe, a JSON.parse akceptuje tylko JSON RFC 8259 i rzuca przy każdym z nich. Czytanie JSONC wymaga parsera takiego jak jsonc-parser albo usunięcia komentarzy przed podaniem tekstu standardowemu parserowi.
Czy komentarze w JSONC powinny przetrwać edycję pliku?
Tak, ale tylko przez API edycji. modify i applyEdits z jsonc-parser zmieniają wskazany węzeł, zachowując pozostałe komentarze; sparsowanie do obiektu i zserializowanie gubi każdy komentarz.