JSON: форматирование, валидация и JSON Schema в реальной работе
Резюме (TL;DR)
В прошлом месяце одна команда правила строчку в feature_flags.json и выкатила её в продакшен. JSON.parse прошёл, CI был зелёный, а на staging выяснилось, что flags.checkout_v2 стоит "true" строкой, и все ветки оплаты свалились на старую реализацию. Фраза «я проверил JSON» на самом деле склеивает три разные операции — форматирование, проверку синтаксиса и проверку структуры — и в тот день команда сделала только первые две. Форматирование (pretty-print) просто расставляет пробелы и переносы строк; оно ничего не проверяет. Синтаксическая валидация сверяет скобки, кавычки и escape-последовательности с RFC 8259; именно этим занимается JSON.parse. Структурная валидация приходит потом и отвечает на вопрос «соответствует ли эта форма тому, чего ждёт мой код?» — ради этого и придуман JSON Schema. Как только эти три шага ставят в один флажок «валидация JSON», появляются бесшумные сбои вроде описанного. Хотите прочитать — форматируйте. Принимаете вход — парсите. А на границах доверия — API, конфиги, сообщения в очередях — валидация через Ajv или аналогичный инструмент со схемой обязательна. Пайплайн, где все три шага стоят на своих местах, надёжнее.
Предыстория и концепции
JSON определён в RFC 8259 (и эквивалентном ECMA-404). Грамматика небольшая и простая. Документ — это строка, число, true, false, null, массив или объект. Строки заключены в двойные кавычки и поддерживают escape-последовательности \n, \t, \", \\, \uXXXX (юникод-кодюнит). Числа не делятся на целые и с плавающей точкой и записываются в десятичной записи. Объект — это неупорядоченный набор пар со строковым ключом, массив — упорядоченный список значений. Пробелы вне строк смысла не имеют.
Не менее важно то, что JSON намеренно не включает: комментариев нет, замыкающих запятых нет, одинарных кавычек, шестнадцатеричных и двоичных литералов тоже. Не-ASCII символы пишутся либо как UTF-8 байты, либо через \u-escape. JSON5 и HJSON — это отдельные форматы с ослабленными правилами, и строгие JSON-парсеры их не принимают.
Успешный разбор означает лишь «синтаксически корректно», а не «семантически верно». {"user": "x", "pass": "y"} — безупречный JSON, но если эндпоинт ждал {"username": "...", "password": "..."}, с точки зрения приложения данные некорректны. Чтобы ловить такие вещи, нужна схема — машиночитаемое «описание допустимых форм документа», — и стандарт для этого — JSON Schema (актуальная мета-схема — Draft 2020-12). Он поддерживает обязательные поля, типы, enum и const, шаблоны строк через регулярные выражения, диапазоны чисел, items и уникальность у массивов, properties и additionalProperties у объектов, композицию через allOf/oneOf/anyOf/$ref. Валидаторы вроде Ajv 8.12 компилируют схему в функцию один раз, поэтому в горячем пути проверка почти бесплатна: в одном API, который я сопровождал, скомпилированная Ajv-валидация занимала десятки микросекунд.
Форматирование — самая простая из трёх задач. Оно не трогает значений, только правит пробелы и отступы: JSON.stringify(obj, null, 2), jq ., встроенный форматтер в IDE. Это для людей; машине оно безразлично.
Сравнение и данные
| Ракурс | Форматирование | Синтаксическая валидация | Валидация JSON Schema |
|---|---|---|---|
| Цель | Сделать читаемым | Проверить, что строка — валидный JSON | Проверить, что распарсенное значение имеет ожидаемую форму |
| Что ловит | Ничего (только пробелы) | Несбалансированные скобки, ошибочные кавычки, плохие escape-последовательности, замыкающие запятые | Отсутствие обязательных полей, несовпадение типов, выход за диапазон, неизвестные ключи |
| Что не ловит | Структуру и семантику | Форма документа и бизнес-правила | Правила вне схемы, связи между полями |
| Типовые инструменты | JSON.stringify(obj, null, 2), jq, форматтер IDE | JSON.parse, jq -e, парсеры в языках | Ajv 8.x, python-jsonschema, OpenAPI-валидаторы |
| Где применяется | Инструменты разработчика, логи | Любая граница парсинга (неявно) | Запрос/ответ API, загрузка конфигов, границы сообщений |
Эти три колонки — не «выберите одну», а последовательные уровни. Посмотреть на package-lock.json весом 12 МБ и привести его к читаемому виду — это форматирование; стоит назвать это «валидацией», и начинается инцидент. Читаем через форматирование, отсекаем битые строки парсингом, проверяем форму схемой. Типичная дыра — сделать первые два шага и забыть третий.
Практические сценарии
Сценарий 1 — отладка ответа API. Внешний платёжный шлюз возвращает 600 строк JSON в одну строку. Вкладка Network в DevTools, локальный форматтер или конвейер вроде curl ... | jq . быстро приводят его к виду, который можно просмотреть глазами. Здесь нет никакой валидации, цель — «визуально найти, где странно», и важно не называть этот шаг «проверкой».
Сценарий 2 — загрузка конфига. Сервис на старте читает config.json. Строгий JSON-парсер поймает, например, замыкающую запятую и не даст стартовать — это правильное поведение. Но, как в примере из вступления, в retries: 3 может оказаться retries: "three", и парсинг пройдёт; проблема вылезет, только когда код попробует сравнить строку с числом. Паттерн, который я предложил одной команде: на первой строке старта вызывать функцию валидации, полученную через Ajv compile(schema), проверять обязательные поля, типы, диапазоны и enum, а при нарушении делать process.exit(1). Пятиминутное изменение уже дважды предотвращало двухдневные staging-аварии.
Сценарий 3 — контрактные тесты OpenAPI. Команда описывает эндпоинты, тела запросов и формы ответов в OpenAPI 3.1 через components.schemas. Контрактные тесты прогоняют примерные payload-ы из спецификации через ту же схему и ловят дрейф, когда серверная реализация начинает возвращать «целое число там, где должна быть строка», — до того как клиент сломается. Движок тот же самый JSON Schema-валидатор; разница только в масштабе применения.
Распространённые заблуждения
«Достаточно JSON.parse — и всё провалидировано». Он проверяет только синтаксис и не смотрит форму. Парсер спокойно вернёт объект, где половина полей пуста. Относитесь к JSON.parse как к фильтру против битых строк, а на границах доверия кладите сверху ещё слой — валидацию по схеме.
«JSON Schema — это только для сервера». Если проверять запрос в браузере ещё до отправки, пользователь сразу получает обратную связь, а нагрузка на сервер падает. Ajv и ряд других валидаторов отлично работают в браузере. Серверная валидация всё равно обязательна — клиентам доверять нельзя, — но клиентская улучшает UX и не ослабляет модель безопасности.
«JSON5 — это JSON с комментариями». JSON5 добавляет комментарии, ключи без кавычек, замыкающие запятые, шестнадцатеричные литералы и прочее. Как формат для ручного редактирования конфигов он приятнее (самый известный его «родственник», tsconfig.json, на самом деле JSONC — ещё одно нестандартное надмножество), но строгие RFC 8259-потребители его не принимают. Используйте JSON5/JSONC только там, где потребитель явно их поддерживает, а при выходе в сеть или к произвольному парсеру отдавайте строгий JSON.
«YAML — это JSON с отступами». Любой валидный JSON — валидный YAML, но YAML поверх добавляет якоря и псевдонимы, типы-теги, несколько документов в одном файле, блочные и сложенные скаляры, парсинг, чувствительный к отступам. Классическая ловушка — «норвежская проблема»: в YAML 1.1 неэкранированное NO читается как булево false. Решение «давайте поменяем JSON на YAML ради читаемости» привлекает новые проблемы.
Чек-лист
- Вы просто хотите прочитать? Только форматируйте. Не называйте это «валидацией».
- Данные пересекают границу доверия (HTTP-запрос, очередь сообщений, конфиг)? Не ограничивайтесь парсингом — добавьте JSON Schema.
- Решили ли вы, разрешать ли неизвестные поля? Ставьте в схему
additionalProperties: falseи определяйте политику отклонения/удаления. - Полезны ли тексты ошибок? Включайте валидатору
allErrors: true(как у Ajv), чтобы сразу показывать все нарушения. - Живёт ли схема рядом с кодом? Дрейф между спецификацией и реализацией меньше, когда схема — единый источник истины для обеих сторон.
- JSON вообще подходит для этого случая? Если конфиг редактируют люди и вы сами выбираете парсер, JSON5/JSONC или TOML дружелюбнее. Для машинного обмена держитесь строгого JSON.
Связанный инструмент
JSON-форматтер Patrache Studio работает в браузере, и вставленный payload не покидает устройство — удобно, когда нужно посмотреть ответ продакшена, содержащий персональные данные. JSON редко ходит один: если внутри запакованы бинарные данные, в Base64 и URL-кодировании разобрано, почему они распухают и когда лучше взять другой транспорт. Если в payload есть идентификаторы, загляните в Сравнение UUID v1/v4/v7 и дизайн первичного ключа, чтобы понять, как версия, выдаваемая сервером, влияет на индексы, сортировку и кэши ниже по стеку.
Источники
- IETF RFC 8259, «The JavaScript Object Notation (JSON) Data Interchange Format» — https://datatracker.ietf.org/doc/html/rfc8259
- Спецификация JSON Schema (Draft 2020-12) — https://json-schema.org/specification
- Ajv — Another JSON Schema Validator — https://ajv.js.org/
- MDN, «Working with JSON» — https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/JSON