Валидация массивов объектов

Работа с массивами объектов в 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-transformer при вложенных массивах

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 не выполняет проверку
  • вложенные DTO остаются plain-object
  • ошибки валидации не возникают там, где ожидаются

Использование 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
  • минимизации глубины вложенности
  • исключения лишних декораторов

Динамические массивы и сложные схемы

При работе с динамическими структурами часто используются:

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

Это позволяет описывать массивы, элементы которых меняют структуру в зависимости от бизнес-логики, сохраняя при этом строгую проверку данных на уровне классов.