@IsIn

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

Основная идея заключается в сравнении входящего значения с массивом разрешённых вариантов. Если значение отсутствует в списке — валидация считается проваленной.


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

Поведение можно выразить следующим образом:

  • существует фиксированный набор допустимых значений
  • входное значение должно совпадать хотя бы с одним элементом набора
  • сравнение выполняется строго (===)

Синтаксис

@IsIn(values: any[], validationOptions?: ValidationOptions)
  • values — массив допустимых значений
  • validationOptions — дополнительные параметры (сообщение об ошибке, группы и т.д.)

Простое использование

import { IsIn } from 'class-validator';

class UserDto {
  @IsIn(['admin', 'user', 'moderator'])
  role: string;
}

В этом случае поле role может принимать только одно из трёх значений.


Поведение при несовпадении

Если значение не входит в список:

const dto = new UserDto();
dto.role = 'superadmin';

Валидация вернёт ошибку:

role must be one of the following values: admin, user, moderator

Работа со строками

Чаще всего @IsIn применяется для строковых перечислений:

class OrderDto {
  @IsIn(['pending', 'paid', 'shipped', 'delivered'])
  status: string;
}

Такой подход заменяет ручные проверки вида status === '...'.


Работа с числами

Допустимо использование числовых наборов:

class RatingDto {
  @IsIn([1, 2, 3, 4, 5])
  rating: number;
}

Важно учитывать, что сравнение строгое, поэтому строка "1" не равна числу 1.


Использование с булевыми значениями

Хотя для boolean обычно используют @IsBoolean, возможен и такой вариант:

class FeatureDto {
  @IsIn([true, false])
  enabled: boolean;
}

Пользовательское сообщение об ошибке

Поведение по умолчанию можно переопределить:

import { IsIn } from 'class-validator';

class ProductDto {
  @IsIn(['book', 'electronics', 'clothing'], {
    message: 'type должен быть одним из: book, electronics, clothing',
  })
  type: string;
}

Влияние типов и трансформации данных

При использовании вместе с class-transformer важно учитывать преобразование типов:

import { Type } from 'class-transformer';
import { IsIn } from 'class-validator';

class TestDto {
  @Type(() => Number)
  @IsIn([10, 20, 30])
  value: number;
}

Без @Type(() => Number) входное значение из JSON может остаться строкой, и проверка не пройдет.


Использование в NestJS DTO

В контексте NestJS валидатор часто применяется в DTO-объектах:

import { IsIn } from 'class-validator';

export class CreateTaskDto {
  @IsIn(['low', 'medium', 'high'])
  priority: string;
}

В сочетании с ValidationPipe входные данные автоматически проверяются до попадания в бизнес-логику.


Поведение с массивами значений

Сам декоратор проверяет одно значение, а не массив входных данных.

Неправильное ожидание:

@IsIn(['a', 'b'])
tags: string[];

Здесь проверяется весь массив как единое значение, что почти всегда приведёт к ошибке.

Корректный подход:

import { IsIn, IsArray } from 'class-validator';

class TagsDto {
  @IsArray()
  @IsIn(['a', 'b', 'c'], { each: true })
  tags: string[];
}

Ключевой момент — параметр each: true, который заставляет валидатор применять проверку к каждому элементу массива.


Сравнение с Enum-подходом

Часто @IsIn используется как альтернатива enum:

enum Role {
  Admin = 'admin',
  User = 'user',
  Moderator = 'moderator',
}

Вариант с enum:

@IsEnum(Role)
role: Role;

Вариант с IsIn:

@IsIn(['admin', 'user', 'moderator'])
role: string;

Различие:

  • @IsEnum — более строгий и типизированный подход
  • @IsIn — более гибкий, особенно при динамических списках

Динамические наборы значений

Допускается использование переменных:

const allowedStatuses = ['draft', 'published', 'archived'];

class PostDto {
  @IsIn(allowedStatuses)
  status: string;
}

Это полезно при централизованном управлении правилами.


Комбинация с другими валидаторами

Часто используется совместно:

import { IsString, IsNotEmpty, IsIn } from 'class-validator';

class AccountDto {
  @IsString()
  @IsNotEmpty()
  @IsIn(['basic', 'pro', 'enterprise'])
  plan: string;
}

Порядок декораторов не влияет на итоговую логику, но влияет на читаемость.


Поведение при undefined и null

  • если поле отсутствует — проверка не выполняется (если не указан @IsDefined)
  • если значение null — валидатор считает его невалидным, так как оно не входит в список

Пример:

class ExampleDto {
  @IsIn(['a', 'b'])
  value: string;
}

null → ошибка undefined → пропуск проверки (если поле не обязательно)


Строгая природа сравнения

Сравнение всегда строгое:

  • '1'1
  • true'true'
  • {}'[object Object]'

Это особенно важно при работе с входящими JSON-данными.


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

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

values.includes(value)

Поэтому:

  • сложность — O(n)
  • оптимально использовать для небольших списков
  • для больших наборов предпочтительнее Set-логика (хотя напрямую не используется в декораторе)

Частые ошибки при использовании

1. Отсутствие each для массивов

@IsIn(['a', 'b'])
tags: string[];

2. Несовпадение типов

@IsIn([1, 2, 3])
value: string; // ошибка логики

3. Динамический список, изменяемый после объявления

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


Практические сценарии применения

  • статусы сущностей (order status, user state)
  • типы ролей и прав доступа
  • фиксированные категории
  • режимы работы системы (development/production/test)
  • whitelist значений для API

Особенности при архитектурном проектировании

Использование @IsIn помогает:

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

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