Зависимые схемы

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

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

В JSON Schema различают два основных типа зависимостей:

  • структурные зависимости (schema dependencies)
  • зависимость наличия свойств (property dependencies)

В Ajv эти механизмы представлены следующим образом:

  • dependentSchemas — применение дополнительной схемы при наличии определённого свойства
  • dependentRequired — требование наличия других свойств при наличии ключевого

dependentSchemas как механизм условной валидации

Ключ dependentSchemas позволяет ассоциировать свойство с полноценной JSON Schema. Если свойство присутствует в объекте, активируется соответствующая схема.

Пример описания банковских данных пользователя:

import Ajv from "ajv";

const ajv = new Ajv();

const schema = {
  type: "object",
  properties: {
    paymentMethod: { type: "string" },
    cardNumber: { type: "string" },
    paypalEmail: { type: "string" }
  },
  dependentSchemas: {
    cardNumber: {
      required: ["cardNumber"],
      properties: {
        billingAddress: { type: "string", minLength: 10 }
      },
      additionalProperties: false
    },
    paypalEmail: {
      required: ["paypalEmail"],
      properties: {
        paypalVerified: { type: "boolean", const: true }
      }
    }
  }
};

const validate = ajv.compile(schema);

В данной схеме наличие cardNumber автоматически включает дополнительную проверку billingAddress. Таким образом, структура объекта становится динамически расширяемой в зависимости от введённых данных.

dependentRequired и классическая зависимость свойств

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

Пример:

const schema = {
  type: "object",
  properties: {
    username: { type: "string" },
    password: { type: "string" },
    otp: { type: "string" }
  },
  dependentRequired: {
    otp: ["password"]
  }
};

Здесь наличие otp невозможно без password. В отличие от dependentSchemas, здесь отсутствует возможность задавать сложные правила валидации — только требования наличия ключей.

Отличие dependentSchemas от dependentRequired

Разграничение этих механизмов принципиально важно при проектировании схем:

  • dependentRequired проверяет только структуру наличия ключей
  • dependentSchemas добавляет полноценную логическую ветку проверки

Сравнение:

Механизм Проверка Гибкость
dependentRequired наличие свойств низкая
dependentSchemas полная JSON Schema логика высокая

Поведение Ajv при компиляции зависимых схем

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

Особенности:

  • зависимости разрешаются на этапе компиляции
  • схемы кэшируются при повторном использовании
  • вложенные зависимости разворачиваются в единый валидатор

При включённом строгом режиме Ajv дополнительно проверяет корректность структуры зависимостей и наличие конфликтующих правил.

Вложенные и каскадные зависимости

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

Пример каскадной структуры:

const schema = {
  type: "object",
  properties: {
    role: { type: "string" },
    adminCode: { type: "string" },
    permissions: { type: "array" }
  },
  dependentSchemas: {
    adminCode: {
      required: ["permissions"],
      properties: {
        permissions: {
          type: "array",
          minItems: 1
        }
      }
    },
    permissions: {
      required: ["role"],
      properties: {
        role: {
          const: "admin"
        }
      }
    }
  }
};

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

Использование зависимостей в прикладных сценариях

Наиболее частые области применения:

  • формы регистрации с разными режимами авторизации
  • платёжные системы с альтернативными методами оплаты
  • конфигурационные файлы с режимами работы
  • API-объекты с условными полями

Пример API-объекта:

const schema = {
  type: "object",
  properties: {
    type: { enum: ["email", "sms"] },
    email: { type: "string" },
    phone: { type: "string" }
  },
  dependentSchemas: {
    email: {
      required: ["email"],
      properties: {
        email: { format: "email" }
      }
    },
    phone: {
      required: ["phone"],
      properties: {
        phone: { pattern: "^[0-9]+$" }
      }
    }
  }
};

Здесь тип сообщения определяет, какие поля становятся валидными и обязательными.

Взаимодействие с if / then / else

В более новых версиях JSON Schema механизм зависимостей частично пересекается с конструкцией if / then / else. В Ajv эти подходы могут использоваться совместно, однако их семантика различается:

  • зависимые схемы ориентированы на наличие ключей
  • условные конструкции опираются на произвольные логические выражения

Пример комбинации:

const schema = {
  type: "object",
  if: {
    properties: { type: { const: "premium" } }
  },
  then: {
    required: ["subscriptionId"]
  },
  dependentSchemas: {
    subscriptionId: {
      properties: {
        subscriptionId: { type: "string" }
      }
    }
  }
};

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

Ошибки валидации и их интерпретация

При нарушении зависимых схем Ajv формирует ошибки, содержащие:

  • путь до объекта
  • ключ зависимости
  • причину нарушения (отсутствие свойства или несоответствие схемы)

Пример типичной ошибки:

data should have required property 'billingAddress'

При использовании dependentSchemas ошибки могут быть более глубокими, поскольку фиксируется не только отсутствие поля, но и провал вложенной схемы.

Производительность зависимых схем

Зависимости увеличивают сложность графа валидации. На практике это влияет на:

  • размер скомпилированной функции
  • глубину проверки объектов
  • количество ветвлений

Оптимизации Ajv:

  • кэширование скомпилированных схем
  • предварительное разрешение зависимостей
  • минимизация повторной проверки общих узлов

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

Типичные ошибки проектирования

Наиболее частые проблемы:

  • циклические зависимости между свойствами
  • дублирование требований в required и dependentRequired
  • избыточное использование dependentSchemas вместо более простых конструкций
  • смешение бизнес-логики и структурной валидации

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

Модель интерпретации зависимостей в Ajv

Внутренняя модель Ajv рассматривает зависимости как граф:

  • узлы — свойства объекта
  • рёбра — условия активации схем
  • листья — конечные проверки типов и ограничений

При валидации объект проходит по этому графу, активируя соответствующие ветки в зависимости от присутствующих ключей.