Комбинирование типов

В JSON Schema комбинирование типов используется для построения сложных правил валидации, когда одного простого ограничения недостаточно. В библиотеке Ajv это реализовано через набор логических операторов схем, позволяющих описывать пересечения, объединения и исключения типов данных.

Основная задача комбинирования — задавать поведение валидатора при работе с альтернативными структурами данных. Вместо того чтобы ограничиваться единственным значением свойства type, используются конструкции anyOf, oneOf, allOf, not, а также комбинации ссылок $ref, позволяющие строить переиспользуемые схемы.


Объединение типов через type

В JSON Schema поле type может принимать не только строку, но и массив значений. Это базовый способ объединения типов без логических операторов.

{
  "type": ["string", "number"]
}

Такая схема допускает строку или число. В Ajv это интерпретируется как простое логическое «ИЛИ» между типами, без дополнительных условий.

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


Комбинация через anyOf

Конструкция anyOf задаёт условие, при котором данные считаются валидными, если хотя бы одна из схем возвращает успех.

{
  "anyOf": [
    { "type": "string", "minLength": 3 },
    { "type": "number", "minimum": 10 }
  ]
}

В данном случае значение может быть либо строкой длиной от трёх символов, либо числом не меньше 10.

Ключевая особенность anyOf заключается в независимости схем: каждая под-схема проверяется отдельно, и достаточно одного успешного результата.

В Ajv это часто используется для описания гибких API-ответов, где структура данных может существенно различаться.


Строгое объединение через oneOf

oneOf требует, чтобы данные соответствовали ровно одной схеме из набора.

{
  "oneOf": [
    { "type": "string", "maxLength": 5 },
    { "type": "string", "minLength": 10 }
  ]
}

Если значение соответствует сразу нескольким схемам, валидация считается неуспешной.

Внутренне Ajv выполняет проверку всех под-схем и подсчитывает количество совпадений. Условие успеха — строго один положительный результат.

oneOf используется в случаях, когда пересечение условий недопустимо и требуется однозначная классификация данных.


Пересечение через allOf

allOf реализует логическое «И» между схемами. Данные должны удовлетворять всем указанным условиям одновременно.

{
  "allOf": [
    { "type": "number" },
    { "minimum": 0 },
    { "maximum": 100 }
  ]
}

Здесь число должно быть одновременно числом, не меньше нуля и не больше ста.

В Ajv allOf часто используется для композиции схем, когда базовая структура расширяется дополнительными ограничениями.


Исключение через not

Конструкция not инвертирует результат проверки схемы.

{
  "not": {
    "type": "string"
  }
}

Такое правило означает, что значение не может быть строкой.

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


Комбинирование с $ref

Одним из ключевых механизмов переиспользования в JSON Schema является $ref. В Ajv он активно применяется для построения модульных схем.

{
  "$ref": "
}

Комбинирование через ссылки позволяет объединять схемы на уровне архитектуры, а не только логики.

Часто используется совместно с allOf:

{
  "allOf": [
    { "$ref": "#/definitions/baseUser" },
    {
      "properties": {
        "role": { "const": "admin" }
      }
    }
  ]
}

Таким образом создаётся расширение базовой схемы без дублирования описания.


Пересечение типов и расширение объектов

В объектных схемах комбинирование особенно важно, так как позволяет строить наследуемые структуры.

{
  "allOf": [
    {
      "type": "object",
      "properties": {
        "id": { "type": "string" }
      },
      "required": ["id"]
    },
    {
      "type": "object",
      "properties": {
        "createdAt": { "type": "string", "format": "date-time" }
      }
    }
  ]
}

Результатом становится объект, содержащий обе группы свойств.

Ajv объединяет такие схемы в единое правило валидации, не создавая промежуточных структур.


Конфликты при комбинировании

При использовании нескольких операторов могут возникать конфликты типов и ограничений. Например:

{
  "allOf": [
    { "type": "string" },
    { "type": "number" }
  ]
}

Такая схема всегда будет невалидной, так как пересечение типов пустое.

Ajv не пытается автоматически разрешать подобные конфликты — результат определяется строго логикой схемы.


Комбинирование с условными конструкциями

Хотя if/then/else формально не относится к логическим операторам объединения, оно часто используется совместно с ними.

{
  "if": {
    "type": "number"
  },
  "then": {
    "minimum": 0
  },
  "else": {
    "type": "string"
  }
}

Такая структура позволяет динамически переключать набор правил в зависимости от входных данных.

В Ajv это обрабатывается как последовательная проверка: сначала условие if, затем соответствующая ветка.


Вложенные комбинации

Сложные схемы часто содержат несколько уровней комбинирования:

{
  "anyOf": [
    {
      "allOf": [
        { "type": "string" },
        { "minLength": 3 }
      ]
    },
    {
      "allOf": [
        { "type": "number" },
        { "minimum": 100 }
      ]
    }
  ]
}

Здесь сначала определяется выбор между ветками (anyOf), а внутри каждой ветки применяется пересечение условий (allOf).

Ajv рекурсивно обходит такие структуры, формируя дерево проверок.


Особенности выполнения в Ajv

При обработке комбинированных схем Ajv оптимизирует валидацию:

  • схемы кешируются после компиляции;
  • allOf может объединяться в одну функцию проверки;
  • oneOf требует полного перебора вариантов;
  • anyOf завершает проверку при первом успехе (если включены оптимизации).

Это делает комбинированные схемы производительными даже при высокой вложенности.


Комбинирование с примитивными ограничениями

Логические операторы часто используются вместе с базовыми ключевыми словами:

  • type
  • enum
  • const
  • format
  • pattern

Например:

{
  "oneOf": [
    { "type": "string", "pattern": "^[a-z]+$" },
    { "type": "string", "enum": ["ADMIN", "USER"] }
  ]
}

Такая схема различает свободный текст и фиксированные значения в рамках одного поля.


Структурная композиция схем

Комбинирование типов в Ajv фактически формирует декларативную систему логики, где каждая схема становится самостоятельным модулем. Эти модули объединяются через:

  • логические операторы;
  • ссылки $ref;
  • вложенные объекты;
  • массивы схем.

Такой подход позволяет строить масштабируемые модели данных без необходимости императивной проверки в коде.