При работе с class-validator основная точка входа для
пакетной проверки — функция validate, принимающая экземпляр
класса и возвращающая массив ошибок ValidationError[]. При
необходимости проверить сразу несколько объектов типовая ошибка
заключается в попытке передать массив напрямую, что не поддерживается
API библиотеки.
Корректная модель пакетной валидации строится через поэлементную обработку массива DTO:
import { validate } fr om "class-validator";
async function validateBatch(dtos) {
const results = await Promise.all(
dtos.map(dto => validate(dto))
);
return results;
}
Каждый элемент массива проходит независимую проверку, а результатом становится массив массивов ошибок.
Возвращаемое значение после обработки массива выглядит как:
ValidationError[][]
Каждый вложенный массив соответствует конкретному объекту.
Пример структуры:
[
[], // объект валиден
[
{
property: "email",
constraints: {
isEmail: "email must be an email"
}
}
]
]
Такой формат требует дополнительной агрегации при формировании ответа API или логики бизнес-обработки.
validateSyncПри отсутствии асинхронных валидаторов возможен синхронный подход:
import { validateSync } fr om "class-validator";
function validateBatchSync(dtos) {
return dtos.map(dto => validateSync(dto));
}
Особенности:
@ValidateIf
с async логикой и т.п.)validateOrReject для пакетной обработкиМетод validateOrReject выбрасывает исключение при
наличии ошибок, что удобно для fail-fast логики:
import { validateOrReject } from "class-validator";
async function validateBatchOrReject(dtos) {
await Promise.all(
dtos.map(dto => validateOrReject(dto))
);
}
Поведение:
try/catchПри больших объёмах данных (тысячи объектов) использование
Promise.all приводит к пиковым нагрузкам на CPU и память.
Более устойчивый подход — контроль concurrency.
Пример реализации через ручной пул:
async function validateBatchWithLimit(dtos, lim it = 10) {
const results = [];
let index = 0;
async function worker() {
while (index < dtos.length) {
const current = index++;
const errors = await validate(dtos[current]);
results[current] = errors;
}
}
const workers = Array.from({ length: lim it }, worker);
await Promise.all(workers);
return results;
}
Такой подход:
В class-validator поддерживается нативная валидация
массивов через @ValidateNested({ each: true }).
Пример DTO:
import { Type } from "class-transformer";
import { ValidateNested, IsString } from "class-validator";
class ItemDto {
@IsString()
name;
}
class BatchDto {
@ValidateNested({ each: true })
@Type(() => ItemDto)
items;
}
В этом случае валидация выполняется рекурсивно внутри одного вызова:
const errors = await validate(batchDto);
Особенности:
childrenclass-transformer для корректной
трансформации типовValidationError в пакетном контекстеПри пакетной обработке важно учитывать вложенность ошибок:
ValidationError {
property: string;
constraints?: Record<string, string>;
children?: ValidationError[];
}
При работе с массивами DTO часто встречается структура:
Пример:
[
{
property: "items",
children: [
{
property: "0",
children: [
{
property: "name",
constraints: {
isString: "name must be a string"
}
}
]
}
]
}
]
Для API-слоёв требуется преобразование сложной структуры в плоский формат:
function flattenErrors(errors, parentPath = "") {
const result = [];
for (const error of errors) {
const path = parentPath
? `${parentPath}.${error.property}`
: error.property;
if (error.constraints) {
result.push({
field: path,
messages: Object.values(error.constraints)
});
}
if (error.children?.length) {
result.push(...flattenErrors(error.children, path));
}
}
return result;
}
Применение:
const batch = await Promise.all(dtos.map(validate));
const flat = batch.map(flattenErrors);
При пакетной обработке важно сохранять индексацию:
const indexed = dtos.map((dto, i) => ({
index: i,
dto,
errors: batchResults[i]
}));
Это позволяет:
В реальных системах часто требуется не прерывать обработку при ошибке одного элемента.
const results = await Promise.allSettled(
dtos.map(dto => validate(dto))
);
Дальнейшая обработка:
const valid = [];
const invalid = [];
results.forEach((r, i) => {
if (r.status === "fulfilled" && r.value.length === 0) {
valid.push(dtos[i]);
} else {
invalid.push({
dto: dtos[i],
errors: r.status === "fulfilled" ? r.value : r.reason
});
}
});
Такой подход обеспечивает:
В большинстве архитектур пакетная валидация выполняется после трансформации:
import { plainToInstance } from "class-transformer";
import { validate } from "class-validator";
const instances = plainToInstance(DtoClass, rawArray);
const results = await Promise.all(
instances.map(validate)
);
Критически важно:
@TypeПри обработке больших batch-наборов применяются следующие техники:
Пример чанкинга:
function chunk(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
async function validateInChunks(dtos) {
const chunks = chunk(dtos, 50);
const results = [];
for (const group of chunks) {
const res = await Promise.all(group.map(validate));
results.push(...res);
}
return results;
}
При увеличении объёма данных проявляются характерные эффекты:
childrenДля оптимизации часто вводится постобработка:
function groupByConstraint(errors) {
const map = new Map();
for (const err of errors) {
if (!err.constraints) continue;
for (const msg of Object.values(err.constraints)) {
map.set(msg, (map.get(msg) || 0) + 1);
}
}
return map;
}
Пакетная проверка DTO в class-validator используется в
нескольких слоях:
Ключевая цель — разделение входного потока на валидные и невалидные элементы без деградации производительности и без блокировки всей операции при единичных ошибках.