Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → JSON Schema: валидация контрактов данных

JSON Schema: как формализовать контракт данных

JSON Schema: типы и ограничения, обязательные поля, версионирование схемы, применение в API и очередях сообщений.

Прямой ответ

JSON Schema — декларативное описание структуры JSON-документа: какие поля допустимы, какого они типа, какие обязательны, какие значения разрешены. Схема существует отдельно от кода, поэтому её можно использовать одновременно в нескольких языках и системах.

Зачем это нужно:

  1. Контракт между сервисами. Потребитель и поставщик данных ссылаются на одну схему, а не на устные договорённости.
  2. Валидация на границе. Данные извне проверяются до того, как попадут в бизнес-логику.
  3. Документация, которая не устаревает. Схема — исполняемое описание формата.
  4. Проверка в тестах. Утверждение «ответ соответствует схеме» заменяет перебор полей.

Актуальная версия спецификации — 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.

Рекомендации:

  1. Задавайте $id и версионируйте. Схема без идентификатора не может быть переиспользована и ссылаться на неё неоткуда.
  2. Не злоупотребляйте format. Валидация форматов по умолчанию отключена в большинстве реализаций; если проверка нужна, её включают явно.
  3. Описывайте сообщения об ошибках. Стандартный вывод валидатора технически точен и нечитаем для пользователя; сопоставление пути к полю с понятным текстом делается на уровне приложения.
  4. Проверяйте схему в CI. Тест, который валидирует набор эталонных документов против схемы, ловит расхождение между кодом и описанием.
  5. Храните схемы рядом с кодом. Схема в отдельном репозитории без автоматической синхронизации быстро устаревает.

Практический сценарий: потребитель молча терял события после изменения формата

Контекст: поставщик добавил в событие поле discount и изменил тип поля total с числа на строку. Обработчик потребителя не падал, но считал суммы неверно.

  1. Диагностика. Обработчик читал поле без проверки типа; строка попадала в расчёт и приводила к конкатенации вместо сложения. Ошибки не возникало, результат был неверным.
  2. Причина. Контракт описывался в документе, а не в схеме; изменение не прошло никакой автоматической проверки.
  3. Действия. (а) Ввели JSON Schema как единственный источник описания события; (б) поставщик проверяет события перед публикацией, потребитель — при получении; (в) в CI добавили проверку совместимости новой версии схемы с предыдущей на наборе эталонных документов; (г) изменение типа поля признали ломающим и выполнили через новую версию события с двойной публикацией на период перехода.
  4. Результат. За следующие полгода было три изменения схемы; каждое прошло проверку совместимости, инцидентов с неверными данными не было.
  5. Профилактика. Правило: любое изменение формата данных начинается с изменения схемы и проверки совместимости, а не с изменения кода.

Вывод: отсутствие формализованного контракта превращает изменение формата в лотерею. Схема не предотвращает изменения, но делает их явными и проверяемыми.

FAQ

Чем JSON Schema отличается от валидации моделями в коде? Схема описывает формат независимо от языка и может использоваться несколькими системами. Модель в коде проверяет данные в одном месте и даёт удобные сообщения, но не переносима. Часто используют и то, и другое: схема для контракта, модель для работы внутри сервиса.

Обязательно ли указывать $schema? Практически да: без него реализация выбирает версию по умолчанию, и поведение может отличаться между библиотеками. Явное указание версии устраняет неоднозначность.

Что делать с полями, которые нужны не всем потребителям? Описывать их как необязательные и документировать назначение. Разделение на несколько схем оправдано, когда наборы полей существенно различаются, иначе число вариантов растёт быстрее, чем польза.

Можно ли генерировать код из схемы? Да, существуют генераторы моделей и клиентов для многих языков. Это снижает расхождение между описанием и кодом, но требует включения генерации в сборку, чтобы сгенерированный код не устаревал.

Источники: JSON Schema docs (getting started, 2020-12 release notes), Python json docs — ссылки в шапке.