Работа с массивами в class-validator отличается от
проверки скалярных значений тем, что требует одновременного контроля
структуры контейнера и каждого его элемента. Библиотека предоставляет
набор декораторов, которые позволяют описывать ограничения как на уровне
массива, так и на уровне вложенных значений.
Ключевая особенность: большинство ограничений для элементов массива
активируются через опцию each: true, которая указывает, что
правило применяется ко всем элементам, а не к самому массиву как к
объекту.
Базовый декоратор для проверки типа — @IsArray().
import { IsArray } from 'class-validator';
export class CreateUserDto {
@IsArray()
roles: string[];
}
Декоратор проверяет только факт того, что значение является массивом. Он не накладывает ограничений на содержимое, длину или тип элементов.
Для применения правил к каждому элементу используется параметр
each: true.
import { IsArray, IsString } from 'class-validator';
export class CreateUserDto {
@IsArray()
@IsString({ each: true })
roles: string[];
}
В этом случае:
Типичная ошибка — отсутствие each: true, что приводит к
проверке всего массива как строки, объекта или другого типа.
Для контроля количества элементов используются:
@ArrayMinSize()@ArrayMaxSize()@ArrayNotEmpty()import {
IsArray,
ArrayMinSize,
ArrayMaxSize,
ArrayNotEmpty,
IsString,
} from 'class-validator';
export class CreateUserDto {
@IsArray()
@ArrayNotEmpty()
@ArrayMinSize(2)
@ArrayMaxSize(5)
@IsString({ each: true })
roles: string[];
}
Поведение:
ArrayNotEmpty() запрещает пустой массивArrayMinSize(n) задаёт минимальное количество
элементовArrayMaxSize(n) ограничивает максимальное количество
элементовЭти декораторы работают независимо от проверки элементов массива.
Массивы строк, чисел и булевых значений — наиболее частый случай.
import { IsArray, IsString, Length } from 'class-validator';
export class CreatePostDto {
@IsArray()
@IsString({ each: true })
@Length(3, 20, { each: true })
tags: string[];
}
Каждый элемент массива проходит полный набор правил.
import { IsArray, IsNumber, Min, Max } from 'class-validator';
export class CreateStatsDto {
@IsArray()
@IsNumber({}, { each: true })
@Min(0, { each: true })
@Max(100, { each: true })
scores: number[];
}
Важно учитывать, что при работе с HTTP-запросами числа часто приходят
как строки, поэтому может потребоваться преобразование через
class-transformer.
Наиболее сложный и часто используемый сценарий — массив объектов.
Для корректной работы требуется комбинация:
@ValidateNested({ each: true })@Type(() => Class) из
class-transformerimport { IsArray, ValidateNested, IsString } from 'class-validator';
import { Type } from 'class-transformer';
class CommentDto {
@IsString()
text: string;
}
export class CreatePostDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => CommentDto)
comments: CommentDto[];
}
Механика работы:
@Type() преобразует plain object в экземпляр
класса@ValidateNested() запускает валидацию вложенных
объектовeach: true обеспечивает обработку каждого элемента
массиваБез @Type() вложенные объекты могут не валидироваться
корректно, так как не будут преобразованы в экземпляры классов.
class-validator поддерживает работу с массивами массивов, но требует явного указания правил на каждом уровне.
import { IsArray, IsNumber, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class MatrixDto {
@IsArray()
@IsArray({ each: true })
@IsNumber({}, { each: true })
matrix: number[][];
}
Более строгий вариант с контролем вложенности через DTO:
class RowDto {
@IsNumber({}, { each: true })
values: number[];
}
export class MatrixDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => RowDto)
matrix: RowDto[];
}
Такой подход предпочтительнее при сложной бизнес-логике, поскольку позволяет валидировать уровни независимо.
Когда стандартных декораторов недостаточно, применяются пользовательские валидаторы.
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
registerDecorator,
} from 'class-validator';
@ValidatorConstraint({ name: 'isUniqueArray', async: false })
export class IsUniqueArrayConstraint implements ValidatorConstraintInterface {
validate(value: any[]) {
return Array.isArray(value) && new Set(value).size === value.length;
}
defaultMessage(args: ValidationArguments) {
return `${args.property} должен содержать уникальные значения`;
}
}
export function IsUniqueArray() {
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName,
validator: IsUniqueArrayConstraint,
});
};
}
Использование:
export class CreateDto {
@IsArray()
@IsUniqueArray()
tags: string[];
}
Кастомные валидаторы особенно полезны для:
@IsString()
roles: string[];
Ошибка: проверяется сам массив, а не его элементы.
@IsArray()
comments: CommentDto[];
Без @ValidateNested вложенные объекты не
валидируются.
@ValidateNested({ each: true })
comments: CommentDto[];
Без @Type(() => CommentDto) объекты остаются plain
object и не проходят корректную проверку.
HTTP-запросы часто передают массивы как строки:
{
"scores": "1,2,3"
}
Без трансформации это не будет массивом, и @IsArray()
вернёт ошибку.
each: true работает только с совместимыми
валидаторамиТипичная конфигурация для массива DTO:
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => ItemDto)
items: ItemDto[];
Для примитивов:
@IsArray()
@IsString({ each: true })
@ArrayMaxSize(10)
tags: string[];
Для числовых ограничений:
@IsArray()
@IsNumber({}, { each: true })
@Min(0, { each: true })
values: number[];