Обязательные свойства

Обязательные свойства в JSON Schema определяются ключевым словом required, которое используется внутри описания объекта и задаёт перечень полей, без которых структура считается невалидной. В Ajv эта проверка выполняется на этапе валидации и приводит к формированию ошибки, если хотя бы одно из указанных свойств отсутствует в проверяемом объекте.

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

Базовая структура:

{
  "type": "object",
  "properties": {
    "id": { "type": "number" },
    "name": { "type": "string" }
  },
  "required": ["id", "name"]
}

В данной схеме объект обязан содержать оба свойства: id и name. Отсутствие любого из них приводит к ошибке валидации.

Ajv интерпретирует required строго: наличие свойства проверяется по ключу объекта, а не по значению и не по типу. Значение null, пустая строка или 0 считаются валидными значениями, если ключ существует.

Поведение Ajv при отсутствии обязательных свойств

При валидации Ajv формирует детализированное сообщение об ошибке, указывая конкретное отсутствующее свойство.

Пример:

const Ajv = require("ajv");
const ajv = new Ajv();

const schema = {
  type: "object",
  properties: {
    email: { type: "string" },
    age: { type: "number" }
  },
  required: ["email", "age"]
};

const validate = ajv.compile(schema);

const data = {
  email: "user@example.com"
};

console.log(validate(data)); 
console.log(validate.errors);

Результат валидации будет отрицательным, а ошибка укажет, что отсутствует свойство age.

Типичная структура ошибки Ajv:

{
  "instancePath": "",
  "schemaPath": "#/required",
  "keyword": "required",
  "params": {
    "missingProperty": "age"
  },
  "message": "must have required property 'age'"
}

Отличие наличия свойства от его значения

Ключевое поведение required заключается в том, что оно проверяет только наличие ключа, но не его содержимое.

Следующий объект считается валидным:

{
  email: "",
  age: null
}

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

Вложенные объекты и локальность required

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

Пример:

{
  "type": "object",
  "properties": {
    "user": {
      "type": "object",
      "properties": {
        "id": { "type": "number" },
        "profile": {
          "type": "object",
          "properties": {
            "nickname": { "type": "string" }
          },
          "required": ["nickname"]
        }
      },
      "required": ["id", "profile"]
    }
  }
}

Здесь:

  • объект user обязан содержать id и profile
  • объект profile обязан содержать nickname

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

Пустой массив required

Если required указан как пустой массив, обязательные свойства отсутствуют:

{
  "type": "object",
  "properties": {
    "a": { "type": "string" }
  },
  "required": []
}

Такой объект всегда валиден с точки зрения обязательных полей, даже если свойства определены в properties.

Отсутствие required в схеме

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

{
  "type": "object",
  "properties": {
    "title": { "type": "string" },
    "description": { "type": "string" }
  }
}

В этом случае объект может содержать любое подмножество свойств или не содержать их вовсе.

required и additionalProperties

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

{
  "type": "object",
  "properties": {
    "id": { "type": "number" }
  },
  "required": ["id"],
  "additionalProperties": false
}

Такой подход задаёт строгую структуру: объект обязан содержать id и не может содержать других свойств.

Ajv проверяет эти условия независимо: отсутствие обязательного поля вызывает одну ошибку, лишние поля — другую.

Динамическая обязательность через if/then

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

Пример:

{
  "type": "object",
  "properties": {
    "type": { "type": "string" },
    "email": { "type": "string" },
    "phone": { "type": "string" }
  },
  "if": {
    "properties": { "type": { "const": "email" } }
  },
  "then": {
    "required": ["email"]
  },
  "else": {
    "required": ["phone"]
  }
}

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

Повторяющиеся и конфликтующие required

В схемах с объединениями (allOf, anyOf, oneOf) несколько required могут накладываться одновременно.

Пример с allOf:

{
  "allOf": [
    {
      "type": "object",
      "required": ["id"]
    },
    {
      "type": "object",
      "required": ["name"]
    }
  ]
}

Итоговая структура требует наличия обоих свойств: id и name. Ajv объединяет ограничения всех подсхем.

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

Поведение при строгом режиме Ajv

В режиме strict Ajv может выдавать предупреждения о потенциально некорректных схемах, например:

  • указание required без соответствующих properties
  • дублирование значений в массиве required
  • несоответствие типов объектов

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

Типичные ошибки при использовании required

Одной из частых проблем является несоответствие имен свойств в required и properties.

Пример некорректной схемы:

{
  "type": "object",
  "properties": {
    "userId": { "type": "number" }
  },
  "required": ["userid"]
}

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

Другой распространённый случай — забытое обновление required при изменении структуры объекта, что приводит к рассинхронизации схемы и реальных данных.

Поведение с отсутствующими объектами

Если сам объект отсутствует или имеет тип, отличный от object, проверка required не выполняется напрямую, а сначала срабатывает проверка type.

{
  "type": "object",
  "required": ["id"]
}

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

Влияние required на архитектуру схем

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

  • описания API-ответов
  • валидации форм
  • проверки конфигурационных файлов
  • обеспечения целостности структур данных

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

Сочетание required с default и nullable

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

Также наличие nullable или type: ["string", "null"] не отменяет обязательность свойства: ключ должен существовать, даже если его значение null.