Nullable и опциональные поля

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

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

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

Отсутствие свойства и поведение валидатора

Если свойство отсутствует, Ajv не выполняет проверку его типа. Это означает, что валидатор работает только с реально присутствующими данными. В случае частичных объектов это позволяет строить гибкие схемы для PATCH-запросов и частичных обновлений.

Особенность поведения проявляется при включённой опции useDefaults. Тогда при наличии default в схеме отсутствующие поля могут быть автоматически добавлены в объект:

{
  "type": "object",
  "properties": {
    "status": {
      "type": "string",
      "default": "active"
    }
  }
}

Nullable-поля и представление null

В JSON значение null является допустимым примитивом, но оно не входит в типы string, number, boolean, object, array. Поэтому для разрешения null используется расширение типа.

Объединение типов

Основной механизм — указание массива типов:

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

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

Этот подход является стандартом JSON Schema и работает во всех актуальных версиях Ajv.

Использование nullable в старых схемах

В более ранних версиях Ajv поддерживался ключ nullable, который упрощал запись:

{
  "type": "string",
  "nullable": true
}

При компиляции такая схема фактически преобразовывалась в объединение типов. Однако в современных версиях предпочтение отдаётся явному union-формату через массив типов, поскольку nullable не является частью базовой спецификации JSON Schema.


Различие между отсутствием поля и null

Отсутствующее поле и поле со значением null семантически различаются и по-разному обрабатываются валидатором.

Отсутствующее поле

{}

Если поле не объявлено в required, объект считается валидным независимо от его типа.

Поле со значением null

{
  "value": null
}

В этом случае проверяется соответствие схемы. Если null не разрешён, возникает ошибка типа.


Совмещение опциональности и nullable

На практике часто требуется одновременно разрешить отсутствие поля и значение null. Эти случаи не эквивалентны и описываются отдельно.

{
  "type": "object",
  "properties": {
    "description": {
      "type": ["string", "null"]
    }
  }
}

Здесь:

  • отсутствие description допустимо, если поле не входит в required
  • null допустим как одно из значений
  • строка допустима как основное значение

Если добавить поле в required, появляется дополнительное ограничение:

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

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


Поведение Ajv при проверке nullable и optional

При валидации Ajv выполняет несколько последовательных шагов:

  1. Проверка наличия обязательных ключей (required)
  2. Проверка типов для существующих полей
  3. Применение дополнительных ключевых слов (например, format, pattern, minimum)
  4. Обработка nullable или union-типов

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

Если поле присутствует со значением null, но null не разрешён, фиксируется ошибка типа:

data.property should be string

Типовые схемы с nullable и optional

Частично заполняемые структуры

{
  "type": "object",
  "properties": {
    "title": { "type": "string" },
    "subtitle": { "type": ["string", "null"] },
    "description": { "type": "string" }
  },
  "required": ["title"]
}

В данной структуре:

  • title обязателен
  • subtitle может отсутствовать или быть null
  • description полностью опционален, но если присутствует — должен быть строкой

API-ответы с неопределёнными значениями

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

  • отсутствие поля для неизвестного значения
  • null для явно неизвестного или отсутствующего значения
{
  "type": "object",
  "properties": {
    "lastLogin": {
      "type": ["string", "null"]
    },
    "phone": {
      "type": "string"
    }
  }
}

Особенности работы в Ajv v8

В актуальных версиях Ajv поведение стало ближе к стандарту JSON Schema:

  • ключ nullable считается устаревшим подходом
  • предпочтение отдаётся union-типам
  • строгая проверка типов усилилась при включённом strict

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


Частичные обновления и PATCH-сценарии

При обработке PATCH-запросов часто требуется различать:

  • отсутствие поля (не изменять значение)
  • null (явно очистить значение)

Схема в таких случаях строится с акцентом на отсутствие required:

{
  "type": "object",
  "properties": {
    "email": {
      "type": ["string", "null"]
    },
    "nickname": {
      "type": "string"
    }
  }
}

Отсутствующие поля игнорируются валидатором, а null проходит только при явном разрешении.


Ошибки, связанные с nullable и optional

Типичные ошибки валидации возникают при смешении ожиданий:

  • передача null вместо строки без разрешения union-типа
  • ожидание проверки отсутствующего поля как ошибки, хотя оно не required
  • использование nullable в версиях Ajv, где ключ не поддерживается

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


Практическое различие моделей данных

Валидация в Ajv опирается на формальную модель данных:

  • ключ отсутствует → значение не существует в объекте
  • ключ присутствует → значение проходит типовую проверку
  • null → отдельный допустимый тип, если он явно указан

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