Зависимости между свойствами

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

Классические зависимости через dependencies

Механизм dependencies в спецификации JSON Schema Draft-07 представляет два основных режима работы: списочные зависимости и схемные зависимости.

Списочные зависимости задают обязательные поля, которые должны присутствовать вместе с указанным свойством.

const schema = {
  type: "object",
  properties: {
    creditCard: { type: "string" },
    billingAddress: { type: "string" },
    cvv: { type: "string" }
  },
  dependencies: {
    creditCard: ["billingAddress", "cvv"]
  }
};

В этом случае наличие creditCard автоматически делает обязательными billingAddress и cvv. Если creditCard отсутствует, ограничения не применяются.

Схемные зависимости позволяют описывать полноценную валидационную логику для набора свойств:

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

Здесь при наличии role объект обязан содержать permissions, причём массив должен соответствовать дополнительным ограничениям.

Современные зависимости dependentRequired

В более новых версиях спецификации JSON Schema (2019-09 и выше), в Ajv применяется dependentRequired, заменяющий списочные зависимости.

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

Механизм работает строго симметрично: если присутствует password, обязательно должен присутствовать username. Это упрощённая и более читаемая форма классического dependencies.

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

Условные зависимости dependentSchemas

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

const schema = {
  type: "object",
  properties: {
    paymentMethod: { type: "string" },
    cardNumber: { type: "string" },
    paypalEmail: { type: "string" }
  },
  dependentSchemas: {
    paymentMethod: {
      if: {
        properties: { paymentMethod: { const: "card" } }
      },
      then: {
        required: ["cardNumber"]
      },
      else: {
        required: ["paypalEmail"]
      }
    }
  }
};

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

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

Хотя if / then / else не является частью dependencies, на практике он часто используется для реализации более гибких зависимостей между свойствами.

const schema = {
  type: "object",
  properties: {
    accountType: { type: "string" },
    companyName: { type: "string" },
    personalId: { type: "string" }
  },
  if: {
    properties: { accountType: { const: "business" } }
  },
  then: {
    required: ["companyName"]
  },
  else: {
    required: ["personalId"]
  }
};

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

Комбинирование зависимостей

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

const schema = {
  type: "object",
  properties: {
    method: { type: "string" },
    token: { type: "string" },
    apiKey: { type: "string" },
    signature: { type: "string" }
  },
  dependentRequired: {
    token: ["signature"]
  },
  dependentSchemas: {
    apiKey: {
      required: ["method"]
    }
  }
};

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

Особенности обработки зависимостей в Ajv

При компиляции схемы Ajv трансформирует зависимости в оптимизированные проверки. Это влияет на производительность:

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

Порядок применения правил определяется структурой схемы, а не порядком описания свойств.

Ограничения и конфликтные ситуации

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

  • пересечение required и dependentRequired может приводить к избыточным условиям;
  • одновременное использование dependencies и if/then/else требует аккуратной логической структуры;
  • циклические зависимости свойств не запрещены синтаксически, но могут усложнять интерпретацию схемы.

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

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

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

  • формы с альтернативными полями;
  • конфигурационные объекты с режимами работы;
  • структуры API-запросов с различными сценариями;
  • бизнес-правила, зависящие от комбинации входных данных.

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