JSON5 и JSONC: JSON с комментариями
Главное: Оба диалекта существуют потому, что конфигурация, написанная людьми, нуждается в комментариях и висячих запятых, но их синтаксис и охват экосистемы различаются, поэтому неверный выбор заедает ваш инструментарий.

Главное: используйте JSONC для комментариев внутри конфигурации VS Code и TypeScript, используйте JSON5, когда нужен более широкий синтаксис литералов ES5.1, например одинарные кавычки или ключи без кавычек, и держите публичные API на стандартном JSON.
Стандартный JSON не допускает комментарии, поэтому каждая команда тихо пишет их всё равно — пока однажды сборка не ломается. Вместо того чтобы делать это втайне, выберите подходящий диалект.
1. У двух диалектов разные мотивы
- JSONC (JSON with Comments): стандартный JSON плюс комментарии и висячие запятые, почти без других синтаксических изменений. Это де-факто стандарт экосистемы VS Code —
settings.json,tsconfig.jsonи.eslintrc.jsonвсе используют его. - JSON5: гораздо более амбициозен, прямо выравниваясь по литералу объекта ES5.1 — одинарные кавычки, ключи без кавычек, шестнадцатеричные числа,
Infinity/NaN, продолжение строки обратной косой чертой и ведущие знаки+.
Одно предложение, чтобы их различить: JSONC лишь делает написание комфортным; JSON5 позволяет писать конфигурационный файл так, будто это JavaScript.
2. Сравнительная таблица синтаксиса
| Возможность | Стандартный JSON | JSONC | JSON5 |
|---|---|---|---|
Комментарии // /* */ |
✗ | ✓ | ✓ |
| Висячие запятые | ✗ | ✓ | ✓ |
| Строки в одинарных кавычках | ✗ | ✗ | ✓ |
| Ключи без кавычек | ✗ | ✗ | ✓ |
| Шестнадцатеричные числа | ✗ | ✗ | ✓ |
Ведущая/завершающая десятичная точка .5 5. |
✗ | ✗ | ✓ |
Числа, начинающиеся с + |
✗ | ✗ | ✓ |
Infinity / NaN |
✗ | ✗ | ✓ |
Продолжение \ в конце строки |
✗ | ✗ | ✓ |
| Многострочные строки | ✗ | ✗ | ✓ (реализуется через продолжение) |
| Произвольное значение верхнего уровня | ✓ | ✓ | ✓ |
Полная спецификация JSON5 — чуть больше одной страницы; пакет json5 давно поддерживается сообществом, а браузеры и Node ведут себя идентично.
3. Парсинг: два API, два вида возможностей позиционирования
3.1 JSON5: простой разбор всего документа
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 возвращает обычный объект без информации о смещениях — потому что он нацелен не на редактирование, а только на «чтение конфигурации».
3.2 JSONC: синтаксическое дерево со смещениями
Ценность jsonc-parser не в парсинге, а в редактировании. Он предлагает три слоя API:
| API | Назначение | Типичное применение |
|---|---|---|
parse |
Разобрать в дерево, несущее offset/length |
Подсветка синтаксиса, сворачивание, навигация |
visit |
Обход в глубину; колбэк получает путь узла | Инспекция в стиле JSONPath |
modify + applyEdits |
Произвести минимальную текстовую правку, затем применить её к исходному тексту | Изменить значение, сохранив комментарии |
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));
Вывод сохраняет исходные комментарии и заменяет только три символа 8080:
{
// port configuration
"server": {
"port": 3000 // 80 in production
}
}
Именно этого не может «редактирование конфигурации регулярными выражениями»: регулярное выражение не знает, какой 8080 менять, и не знает, что 8080 внутри строки нужно оставить в покое.
3.3 Сообщения об ошибках должны нести смещения
Коды ошибок jsonc-parser — значения перечисления; распространённые:
| Код ошибки | Значение |
|---|---|
InvalidSymbol |
Появился символ, которого не должно быть |
PropertyNameExpected |
Ожидалось имя ключа |
ColonExpected |
Ожидалось двоеточие |
CommaExpected |
Ожидалась запятая |
CloseBraceExpected |
Объект не закрыт |
UnexpectedEndOfComment |
Комментарий не закрыт |
InvalidEscapeCharacter |
Неверная escape-последовательность |
Когда offset преобразован в строку и столбец, редактор может нарисовать волну точно на проблемной позиции.
4. Когда что использовать
| Сценарий | Рекомендация | Причина |
|---|---|---|
| Тела запросов/ответов публичного API | Стандартный JSON | Диалекты не поддерживаются универсальными клиентами |
package.json, composer.json |
Стандартный JSON | Менеджеры пакетов понимают только стандартный JSON |
tsconfig.json, .vscode/settings.json |
JSONC | По умолчанию в экосистеме, поддерживается инструментарием нативно |
| Конфигурация приложения (нужны комментарии) | JSONC или YAML | Комментарии должны сохраняться, пока инструменты всё ещё могут читать и писать |
Нужны Infinity, шестнадцатеричные числа, сложные литералы |
JSON5 | Самая сильная выразительность |
| Обмен данными, логи, кеши | Формат с разделением по строкам или стандартный JSON | Требуются потоковость и универсальность |
Правило большого пальца: если файл будет читать «нечеловеческий инструмент», предпочитайте стандартный JSON; рассматривайте диалект только когда уверены, что его читает исключительно ваш собственный код.
5. Три вида риска, которые вносят диалекты
5.1 Расползание требования
Когда другой инструмент читает конфигурационный файл JSON5 и вызывает JSON.parse, он падает начисто. Типичный симптом — «локально работает, падает в Docker» — потому что контейнер запускает другой путь чтения.
Мера противодействия: добавьте в репозиторий проверку CI, которая явно перечисляет, какие файлы могут использовать диалект; всё остальное валидируется как стандартный JSON.
5.2 Сюрпризы вокруг вывода типов
JSON5 допускает ключи без кавычек и одинарные кавычки, но не позволяет вносить литеральные конструкции, такие как даты или регулярные выражения — это всё ещё формат данных, а не вычисление JavaScript.
Люди предполагали, что JSON5 может выразить new Date(), и видели, как парсинг падает. JSON5 — не eval.
5.3 Комментарии молча отбрасываются
Ни JSON5.stringify, ни JSON.stringify не сохраняют комментарии. Поэтому «прочитать, поправить значение, записать обратно» теряет все комментарии.
Когда комментарии должны выжить, единственные варианты — API редактирования jsonc-parser или библиотека YAML, поддерживающая сохранение комментариев.
6. Практические рекомендации
- [ ] Держите в командной документации таблицу «тип файла → формат», чтобы никто не импровизировал;
- [ ] Оберните чтение конфигурации в единый
loadConfig(), который внутри решает, какой парсер использовать, чтобы бизнес-код никогда не вызывалJSON.parseнапрямую; - [ ] При программном изменении конфигурации с комментариями используйте
modify+applyEdits; никогда не переписывайте весь файл; - [ ] Валидируйте файлы диалектов в CI тоже (
jsonc-parserдля JSONC,JSON5.parseдля JSON5), иначе синтаксические ошибки всплывают только во время выполнения; - [ ] Используйте стандартный JSON для публичных интерфейсов, ключей кеша и полезных нагрузок очередей сообщений.
Вопросы и ответы
Как выбрать между JSON5 и JSONC?
Выбирайте по среде выполнения, а не по количеству синтаксиса: VS Code, TypeScript и ESLint читают JSONC по умолчанию, поэтому JSONC даёт лучшую совместимость; обращайтесь к JSON5 только когда нужен более широкий синтаксис литералов ES5.1, например одинарные кавычки, ключи без кавычек или Infinity. Ни один диалект не подходит для ответов публичных API.
Почему стандартный парсер JSON не может читать JSONC напрямую?
JSONC допускает комментарии и висячие запятые, тогда как JSON.parse принимает только JSON по RFC 8259 и падает на обоих. Чтение JSONC требует парсер вроде jsonc-parser или удаления комментариев, прежде чем передать текст стандартному парсеру.
Должны ли комментарии в JSONC выживать при правках файла?
Да, но только через API редактирования. modify и applyEdits в jsonc-parser меняют целевой узел, сохраняя остальные комментарии; разбор в объект и стрингификация отбрасывает все комментарии.