Лаборатория кодера

Справочник · Данные и форматы

JSON Schema: как описать и проверить структуру JSON

Ключевые слова черновика 2020-12 с примерами: типы, строки и числа, объекты и массивы, ссылки, условия и типичные ошибки в схемах.

Обновлено · 6 мин чтения

JSON Schema — это JSON-документ, который описывает, какими должны быть другие JSON-документы. По схеме проверяют конфиги, тела запросов к API и ответы, по ней же редакторы подсказывают поля, а генераторы делают типы для TypeScript, Go и Python. Ниже — актуальная версия стандарта, черновик 2020-12.

Первая схема

json
{
  "title": "Пользователь",
  "type": "object",
  "properties": {
    "id":    { "type": "integer", "minimum": 1 },
    "email": { "type": "string", "format": "email" },
    "name":  { "type": "string", "minLength": 1, "maxLength": 100 },
    "roles": {
      "type": "array",
      "items": { "enum": ["admin", "editor", "viewer"] },
      "uniqueItems": true
    }
  },
  "required": ["id", "email"],
  "additionalProperties": false
}

Этому документу схема соответствует:

json
{ "id": 7, "email": "olga@example.com", "roles": ["editor"] }

А этому нет: id строкой, неизвестное поле age и повтор в roles:

json
{ "id": "7", "email": "olga@example.com", "age": 30, "roles": ["viewer", "viewer"] }

Типы

typeЧто подходитПримечание
"string"строка
"number"любое число
"integer"число без дробной части1.0 тоже считается целым
"boolean"true, false
"null"null
"object"объект
"array"массив
["string", "null"]строка или nullтак описывают необязательные значения

Схема без type разрешает любой тип. Пустая схема {} и true принимают всё, false — ничего.

Строки и числа

КлючПримерСмысл
minLength, maxLength"maxLength": 64длина в символах Юникода, а не в байтах
pattern"pattern": "^[a-z0-9-]+$"регулярное выражение по правилам ECMA-262
format"format": "date-time"date, time, email, uri, uuid, ipv4, ipv6, hostname и другие
enum"enum": ["low", "high"]значение из списка
const"const": "v2"ровно это значение
minimum, maximum"minimum": 0границы включительно
exclusiveMinimum, exclusiveMaximum"exclusiveMaximum": 100границы не включаются
multipleOf"multipleOf": 0.01кратность, например копейки

Объекты

КлючЧто задаёт
propertiesсхемы для известных полей
requiredсписок обязательных полей
additionalPropertiesсхема или false для всех полей, не упомянутых в properties и patternProperties
patternPropertiesсхемы для полей, чьё имя подходит под регулярное выражение
propertyNamesограничение на сами имена полей
minProperties, maxPropertiesчисло полей
dependentRequiredесли есть поле A, обязательны B и C
dependentSchemasесли есть поле A, весь объект должен подходить под схему
unevaluatedPropertiesкак additionalProperties, но видит поля из allOf, $ref и условий
json
{
  "type": "object",
  "properties": {
    "card":   { "type": "string" },
    "expiry": { "type": "string", "pattern": "^(0[1-9]|1[0-2])/[0-9]{2}$" }
  },
  "dependentRequired": { "card": ["expiry"] },
  "patternProperties": { "^x-": {} },
  "additionalProperties": false
}

Здесь поле expiry обязательно, только когда передан card, а любые поля с префиксом x- разрешены без проверки.

Массивы

В 2020-12 ключи для массивов переделали, и старые примеры из интернета часто не работают.

КлючЧто задаёт
itemsсхема для всех элементов (после prefixItems, если он есть)
prefixItemsмассив схем для элементов по позициям: кортеж
items: falseзапрет на элементы сверх prefixItems
containsхотя бы один элемент подходит под схему
minContains, maxContainsсколько элементов должно подойти под contains
minItems, maxItemsдлина массива
uniqueItemsвсе элементы разные
json
{
  "type": "array",
  "prefixItems": [
    { "type": "number", "description": "широта" },
    { "type": "number", "description": "долгота" }
  ],
  "items": false
}

Повторное использование: $defs и $ref

json
{
  "$defs": {
    "money": {
      "type": "object",
      "properties": {
        "amount":   { "type": "integer", "minimum": 0 },
        "currency": { "enum": ["RUB", "USD", "EUR"] }
      },
      "required": ["amount", "currency"]
    }
  },
  "type": "object",
  "properties": {
    "price":    { "$ref": "#/$defs/money" },
    "discount": { "$ref": "#/$defs/money" }
  }
}

Ссылка #/$defs/money — это указатель JSON Pointer внутри того же документа. Ссылаться можно и на другой файл: "$ref": "money.json" разрешается относительно $id текущей схемы. В 2020-12 рядом с $ref можно писать другие ключи, они применяются вместе с ним.

Комбинации и условия

КлючДокумент подходит, если
allOfподходит под все схемы списка
anyOfподходит хотя бы под одну
oneOfподходит ровно под одну
notне подходит под схему
if / then / elseесли подходит под if, проверяется then, иначе else
json
{
  "type": "object",
  "properties": {
    "delivery": { "enum": ["courier", "pickup"] },
    "address":  { "type": "string" },
    "point_id": { "type": "integer" }
  },
  "required": ["delivery"],
  "if":   { "properties": { "delivery": { "const": "courier" } } },
  "then": { "required": ["address"] },
  "else": { "required": ["point_id"] }
}

Аннотации

Ключи title, description, default, examples, deprecated, readOnly, writeOnly и $comment ничего не проверяют. Их читают редакторы, генераторы документации и форм. Особенно важно помнить про default: валидатор не подставляет значение по умолчанию в документ, это делает ваш код.

Где проверять

  • Python: пакет jsonschema, класс Draft202012Validator.
  • JavaScript: библиотека Ajv, для 2020-12 импортируется отдельный класс Ajv2020, форматы подключаются пакетом ajv-formats.
  • Go: santhosh-tekuri/jsonschema.
  • Редакторы: VS Code и JetBrains подхватывают схему из поля $schema в JSON-файле или из настроек проекта и подсказывают поля прямо при наборе.

Сам JSON перед проверкой удобно отформатировать в форматтере, а YAML-конфиг сначала перевести в JSON конвертером: схемы работают и для YAML, потому что модель данных та же.