JSON Toolkit

JSON5 и JSONC: JSON с комментариями

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

Начинающий~10 мин чтенияОпубликовано 2026-10-30Обновлено 2026-10-30
Обложка для 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 меняют целевой узел, сохраняя остальные комментарии; разбор в объект и стрингификация отбрасывает все комментарии.