Перечисления

Ключевое слово enum в JSON Schema определяет фиксированный набор допустимых значений для данных. В контексте Ajv (Another JSON Schema Validator) перечисления компилируются в высокоэффективные проверки, что делает их одним из самых быстрых способов валидации ограниченных доменных значений.

Базовая семантика enum

Перечисление задаёт список допустимых литералов. Значение считается валидным, если оно строго совпадает с одним из элементов массива.

{
  "type": "string",
  "enum": ["pending", "active", "disabled"]
}

В Ajv проверка выполняется через строгие сравнения (=== для примитивов), поэтому тип имеет принципиальное значение. Значение "1" и 1 считаются различными.

Строковые перечисления

Наиболее распространённый вариант — строковые enum. Они часто используются для статусов, ролей, типов сущностей.

const schema = {
  type: "object",
  properties: {
    status: {
      type: "string",
      enum: ["draft", "published", "archived"]
    }
  },
  required: ["status"]
};

Особенность Ajv заключается в том, что такой enum компилируется в набор быстрых сравнений, оптимизированных под V8.

Числовые перечисления

Числовые enum используются для фиксированных кодов или классификаторов.

const schema = {
  type: "object",
  properties: {
    level: {
      type: "number",
      enum: [10, 20, 30]
    }
  }
};

Важно учитывать, что 10 и "10" не равны, даже если включена частичная коэрсия типов.

Смешанные перечисления

JSON Schema допускает смешанные типы внутри enum, но в практике Ajv это требует осторожности из-за строгого сравнения типов.

const schema = {
  enum: ["1", 1, true, null]
};

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

enum и const

const представляет частный случай enum с единственным допустимым значением.

{ "const": "active" }

Эквивалент:

{ "enum": ["active"] }

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

Перечисления в объектах

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

const schema = {
  type: "object",
  properties: {
    role: {
      type: "string",
      enum: ["user", "admin", "moderator"]
    },
    priority: {
      type: "integer",
      enum: [0, 1, 2]
    }
  },
  required: ["role"]
};

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

Перечисления в массивах

Enum может применяться к элементам массива через items.

const schema = {
  type: "array",
  items: {
    type: "string",
    enum: ["red", "green", "blue"]
  }
};

Каждый элемент массива проверяется независимо.

Вложенные перечисления

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

const schema = {
  type: "object",
  properties: {
    config: {
      type: "object",
      properties: {
        mode: {
          type: "string",
          enum: ["auto", "manual"]
        }
      }
    }
  }
};

Ajv рекурсивно компилирует такие схемы в оптимизированные валидаторы.

Ошибки валидации enum

При несоответствии значению из списка Ajv формирует ошибку с указанием допустимых значений.

Типичный формат ошибки:

  • should be equal to one of the allowed values

Список допустимых значений может быть включён в расширенный вывод ошибок при использовании allErrors и verbose режимов.

Производительность перечислений

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

  • преобразуется в lookup-структуры или цепочку сравнений;
  • избегает регулярных выражений;
  • не требует дополнительных вычислений;
  • работает за O(n), где n — длина массива enum (обычно мала).

Для небольших списков (<20 элементов) производительность близка к константной.

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

В строгом режиме Ajv может предупреждать о потенциальных проблемах схем:

  • дублирующиеся значения в enum
  • несоответствие type и элементов enum
  • использование несовместимых типов
const ajv = new Ajv({ strict: true });

Дубликаты внутри enum не запрещены стандартом, но считаются логической ошибкой.

Coercion типов и enum

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

const ajv = new Ajv({ coerceTypes: true });

Например:

  • вход "1"
  • схема enum: [1]

Результат зависит от порядка коэрсии: если строка будет преобразована в число, проверка пройдёт, иначе нет.

Dynamic enum через $data

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

const schema = {
  type: "object",
  properties: {
    allowed: { type: "array", items: { type: "string" } },
    value: {
      type: "string",
      enum: { $data: "1/allowed" }
    }
  }
};

В этом случае список допустимых значений берётся из поля allowed текущего объекта.

Комбинация enum с oneOf

В более сложных схемах enum заменяется или комбинируется с oneOf, когда каждому значению соответствует своя структура.

const schema = {
  oneOf: [
    {
      properties: { type: { const: "A" }, data: { type: "string" } }
    },
    {
      properties: { type: { const: "B" }, data: { type: "number" } }
    }
  ]
};

Такой подход предпочтителен, когда значения перечисления определяют разные типы данных.

Отличие enum от pattern

Для строковых значений часто возникает выбор между enum и pattern:

  • enum — фиксированный набор значений
  • pattern — правило формирования строки
// enum
{ enum: ["red", "green", "blue"] }

// pattern
{ type: "string", pattern: "^(red|green|blue)$" }

enum предпочтительнее из-за точности и производительности.

Пустой enum

Пустой массив в enum делает схему невыполнимой:

{ enum: [] }

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

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

  • Несовпадение типов (число vs строка)
  • Использование больших списков без необходимости (вместо справочников)
  • Дублирование значений
  • Попытка заменить enum сложной логикой, где лучше использовать oneOf

Роль enum в архитектуре схем

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

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