Прямой ответ
JSON Schema — декларативное описание структуры JSON-документа: какие поля допустимы, какого они типа, какие обязательны, какие значения разрешены. Схема существует отдельно от кода, поэтому её можно использовать одновременно в нескольких языках и системах.
Зачем это нужно:
- Контракт между сервисами. Потребитель и поставщик данных ссылаются на одну схему, а не на устные договорённости.
- Валидация на границе. Данные извне проверяются до того, как попадут в бизнес-логику.
- Документация, которая не устаревает. Схема — исполняемое описание формата.
- Проверка в тестах. Утверждение «ответ соответствует схеме» заменяет перебор полей.
Актуальная версия спецификации — 2020-12. Важное изменение относительно ранних черновиков — ключевое слово $defs вместо definitions и уточнённая семантика $schema.
{
"$schema": "",
"$id": "",
"title": "OrderEvent",
"type": "object",
"required": ["event_id", "occurred_at", "order"],
"additionalProperties": false,
"properties": {
"event_id": { "type": "string", "format": "uuid" },
"occurred_at": { "type": "string", "format": "date-time" },
"order": {
"type": "object",
"required": ["id", "total", "currency"],
"properties": {
"id": { "type": "integer", "minimum": 1 },
"total": { "type": "number", "exclusiveMinimum": 0 },
"currency": { "enum": ["RUB", "USD", "EUR"] },
"items": {
"type": "array", "minItems": 1,
"items": { "$ref": "#/$defs/orderItem" }
}
}
}
},
"$defs": {
"orderItem": {
"type": "object",
"required": ["sku", "qty"],
"properties": {
"sku": { "type": "string", "pattern": "^[A-Z]{2}-[0-9]{2}$" },
"qty": { "type": "integer", "minimum": 1, "maximum": 9999 }
}
}
}
}
Проверка в Python:
from jsonschema import Draft202012Validator
validator = Draft202012Validator(schema)
errors = sorted(validator.iter_errors(payload), key=lambda e: e.json_path)
for e in errors:
logger.warning("schema violation", extra={"path": e.json_path, "msg": e.message})
Ключевое решение при проектировании — additionalProperties. Значение false запрещает неизвестные поля: это строгий контракт, который ловит ошибки, но ломается при добавлении поля поставщиком. Значение true (по умолчанию) разрешает любые дополнительные поля: гибче, но пропускает опечатки в именах.
Практика: версионирование и применение
1. Совместимость изменений схемы. Не все изменения равнозначны:
| Изменение | Совместимость |
|---|---|
| Добавление необязательного поля | Совместимо |
| Добавление обязательного поля | Ломает потребителей |
Расширение enum новыми значениями |
Ломает потребителей, которые не ожидают новых значений |
Сужение enum |
Ломает поставщика |
| Ужесточение формата или диапазона | Ломает существующие данные |
| Ослабление ограничений | Совместимо |
Практика: версия схемы входит в идентификатор, а потребители проверяют, что понимают данную версию. О развитии схем — в статье про реестр схем и Avro.
2. Схема в API. OpenAPI описывает тела запросов и ответов через JSON Schema, поэтому одна схема используется и для валидации, и для генерации документации — см. контрактную разработку API.
3. Схема в очередях. Событие, публикуемое в брокер, проверяется до публикации. Это дешевле, чем разбирать ошибку на стороне нескольких потребителей.
4. Схема в конфигурации. Файлы настроек проверяются при старте: ошибка в конфигурации обнаруживается сразу, а не в момент использования неверного значения.
5. Валидация на границе, а не везде. Проверка каждого внутреннего вызова замедляет код и дублирует работу. Достаточно проверять данные на входе в систему и на выходе наружу; внутри работают уже проверенные структуры — о валидации в веб-слое в статье про FastAPI.
Рекомендации:
- Задавайте
$idи версионируйте. Схема без идентификатора не может быть переиспользована и ссылаться на неё неоткуда. - Не злоупотребляйте
format. Валидация форматов по умолчанию отключена в большинстве реализаций; если проверка нужна, её включают явно. - Описывайте сообщения об ошибках. Стандартный вывод валидатора технически точен и нечитаем для пользователя; сопоставление пути к полю с понятным текстом делается на уровне приложения.
- Проверяйте схему в CI. Тест, который валидирует набор эталонных документов против схемы, ловит расхождение между кодом и описанием.
- Храните схемы рядом с кодом. Схема в отдельном репозитории без автоматической синхронизации быстро устаревает.
Практический сценарий: потребитель молча терял события после изменения формата
Контекст: поставщик добавил в событие поле discount и изменил тип поля total с числа на строку. Обработчик потребителя не падал, но считал суммы неверно.
- Диагностика. Обработчик читал поле без проверки типа; строка попадала в расчёт и приводила к конкатенации вместо сложения. Ошибки не возникало, результат был неверным.
- Причина. Контракт описывался в документе, а не в схеме; изменение не прошло никакой автоматической проверки.
- Действия. (а) Ввели JSON Schema как единственный источник описания события; (б) поставщик проверяет события перед публикацией, потребитель — при получении; (в) в CI добавили проверку совместимости новой версии схемы с предыдущей на наборе эталонных документов; (г) изменение типа поля признали ломающим и выполнили через новую версию события с двойной публикацией на период перехода.
- Результат. За следующие полгода было три изменения схемы; каждое прошло проверку совместимости, инцидентов с неверными данными не было.
- Профилактика. Правило: любое изменение формата данных начинается с изменения схемы и проверки совместимости, а не с изменения кода.
Вывод: отсутствие формализованного контракта превращает изменение формата в лотерею. Схема не предотвращает изменения, но делает их явными и проверяемыми.
FAQ
Чем JSON Schema отличается от валидации моделями в коде? Схема описывает формат независимо от языка и может использоваться несколькими системами. Модель в коде проверяет данные в одном месте и даёт удобные сообщения, но не переносима. Часто используют и то, и другое: схема для контракта, модель для работы внутри сервиса.
Обязательно ли указывать $schema?
Практически да: без него реализация выбирает версию по умолчанию, и поведение может отличаться между библиотеками. Явное указание версии устраняет неоднозначность.
Что делать с полями, которые нужны не всем потребителям? Описывать их как необязательные и документировать назначение. Разделение на несколько схем оправдано, когда наборы полей существенно различаются, иначе число вариантов растёт быстрее, чем польза.
Можно ли генерировать код из схемы? Да, существуют генераторы моделей и клиентов для многих языков. Это снижает расхождение между описанием и кодом, но требует включения генерации в сборку, чтобы сгенерированный код не устаревал.
Источники: JSON Schema docs (getting started, 2020-12 release notes), Python json docs — ссылки в шапке.