Работа с массивами объектов в class-validator требует
одновременного применения декораторов валидации и корректной
трансформации входных данных. Основная сложность заключается в том, что
библиотека проверяет свойства классов, а не «сырые» JavaScript-объекты,
поэтому без предварительного преобразования вложенные структуры не будут
валидироваться корректно.
Для начала массив должен быть явно помечен как массив с помощью декоратора:
import { IsArray } from 'class-validator';
export class CreateUserDto {
@IsArray()
tags;
}
Такой подход проверяет только факт того, что значение является массивом. Содержимое массива при этом не анализируется.
Если требуется ограничение по типу элементов, одного
IsArray недостаточно.
Для массивов строк, чисел или булевых значений используется
комбинация IsArray и декораторов типа с опцией
each: true.
import { IsArray, IsString } from 'class-validator';
export class CreateUserDto {
@IsArray()
@IsString({ each: true })
tags;
}
Механика each: true означает применение правила ко всем
элементам массива.
Аналогично для чисел:
import { IsArray, IsInt } from 'class-validator';
export class ProductDto {
@IsArray()
@IsInt({ each: true })
prices;
}
eachСложные структуры требуют использования вложенной валидации через
ValidateNested.
import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
class AddressDto {
street;
city;
}
export class CreateUserDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => AddressDto)
addresses;
}
Здесь ключевыми являются три компонента:
IsArray — проверка структуры контейнераValidateNested({ each: true }) — рекурсивная валидация
каждого объектаType(() => AddressDto) — преобразование plain-object
в экземпляр классаБез Type вложенные объекты останутся обычными объектами,
и валидация может быть частично или полностью не выполнена.
class-validator не выполняет автоматическое
преобразование типов. Поэтому входные данные вида JSON:
{
"addresses": [
{ "street": "A", "city": "B" }
]
}
не станут экземплярами AddressDto без:
@Type(() => AddressDto)
Это критически важно для корректной работы вложенной валидации.
При работе с вложенными структурами требуется комбинирование
each: true на каждом уровне:
class ItemDto {
name;
}
class GroupDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => ItemDto)
items;
}
export class RootDto {
@IsArray()
@ValidateNested({ each: true })
@Type(() => GroupDto)
groups;
}
В таких структурах:
TypeВ реальных сценариях массивы часто содержат объекты с необязательными
полями. Для этого применяется PartialType или
skipMissingProperties.
Пример через class-validator:
import { IsOptional, IsString } from 'class-validator';
class AddressDto {
@IsOptional()
@IsString()
street;
@IsOptional()
@IsString()
city;
}
При такой конфигурации элементы массива могут быть неполными, но валидными.
Часто требуется контроль размера коллекции:
import { ArrayMinSize, ArrayMaxSize } from 'class-validator';
export class CreateUserDto {
@IsArray()
@ArrayMinSize(1)
@ArrayMaxSize(5)
tags;
}
Поведение:
ArrayMinSize ограничивает минимальное количество
элементовArrayMaxSize ограничивает максимальное количество
элементовНаиболее распространённый паттерн включает сразу несколько уровней проверок:
import {
IsArray,
ValidateNested,
ArrayMinSize,
IsString
} from 'class-validator';
import { Type } from 'class-transformer';
class TagDto {
@IsString()
name;
}
export class CreatePostDto {
@IsArray()
@ArrayMinSize(1)
@ValidateNested({ each: true })
@Type(() => TagDto)
tags;
}
Такой подход обеспечивает:
Отсутствие Type при вложенных объектах приводит к тому,
что:
ValidateNested не выполняет проверкуИспользование each: true без IsArray может
приводить к некорректной интерпретации входных данных.
Некорректная комбинация декораторов:
@ValidateNested({ each: true })
@IsString({ each: true })
tags;
Такая конфигурация противоречива и приводит к непредсказуемому поведению, так как одновременно предполагается объектная и примитивная структура.
В сложных схемах возможна зависимость правил от контекста:
import { ValidateIf, IsString } from 'class-validator';
class ItemDto {
@ValidateIf(o => o.type === 'text')
@IsString()
value;
}
В сочетании с массивами это позволяет описывать гетерогенные структуры, где элементы могут различаться по типу.
При валидации больших коллекций важно учитывать:
each: true увеличивает количество проверок линейноОптимизация достигается за счёт:
ArrayMaxSizeПри работе с динамическими структурами часто используются:
ValidateNested с пользовательскими
валидаторамиЭто позволяет описывать массивы, элементы которых меняют структуру в зависимости от бизнес-логики, сохраняя при этом строгую проверку данных на уровне классов.