Ошибки валидации в class-validator почти всегда связаны
не с самой библиотекой, а с несоответствием между моделью данных и
правилами декораторов. На практике наиболее частые источники
проблем:
whitelist,
forbidNonWhitelisted)validate()Каждый из этих случаев проявляется одинаково: валидатор возвращает неожиданные ошибки или, наоборот, пропускает некорректные данные.
Основной цикл работы библиотеки строится вокруг функции
validate:
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';
class User {
name;
}
const input = { name: 'Alex' };
const instance = plainToInstance(User, input);
validate(instance).then(errors => {
console.log(errors);
});
Ключевая проблема возникает, когда пропускается этап преобразования
через plainToInstance. В этом случае декораторы могут не
срабатывать корректно, особенно при строгой типизации.
class-validator не выполняет автоматическое приведение
типов. Значение "123" остаётся строкой, даже если ожидается
число.
Типичный сбой:
import { IsNumber } from 'class-validator';
class Product {
@IsNumber()
price;
}
const input = { price: "100" };
Валидация завершится ошибкой, потому что строка не преобразуется автоматически.
Решение связано с использованием трансформации:
import { Type } from 'class-transformer';
class Product {
@Type(() => Number)
@IsNumber()
price;
}
Основной инструмент диагностики — структура ошибок:
[
{
property: 'price',
constraints: {
isNumber: 'price must be a number'
},
children: [],
value: 'abc'
}
]
Каждое поле содержит:
property — имя поляconstraints — набор нарушенных правилvalue — фактическое значениеchildren — вложенные ошибкиПри сложной валидации именно children часто остаются без
внимания, что приводит к ощущению «пустых» ошибок.
При работе с вложенными структурами требуется явное указание
@ValidateNested() и @Type().
import { ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
class Address {
street;
}
class User {
@ValidateNested()
@Type(() => Address)
address;
}
Если @Type() отсутствует, вложенные валидаторы не
активируются, и объект проходит проверку некорректно.
Эти настройки часто создают иллюзию «сломавшейся» валидации.
import { validate } from 'class-validator';
validate(user, {
whitelist: true,
forbidNonWhitelisted: true
});
Различие:
whitelist — удаляет лишние поляforbidNonWhitelisted — выбрасывает ошибку при наличии
лишних полейЧастая проблема возникает при неожиданных свойствах в payload. Ошибка при этом не связана с декораторами, а вызвана именно конфигурацией.
При использовании @Validate() с асинхронной логикой
поведение становится зависимым от Promise.
import { ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator';
@ValidatorConstraint({ async: true })
class IsUnique implements ValidatorConstraintInterface {
async validate(value) {
return await databaseCheck(value);
}
}
Типичные ошибки:
await при вызове validatevalidateSync, который игнорирует
async-валидаторыАсинхронные правила всегда требуют использования
validate, а не синхронной версии.
validate(instance);
validateSync(instance);
Сравнение:
validate — поддерживает async, возвращает PromisevalidateSync — синхронный, игнорирует
async-декораторыСкрытая ошибка возникает, когда часть правил работает только в асинхронном режиме, но вызывается синхронный метод.
Кастомные валидаторы часто содержат логику, которая трудно отслеживается в общей цепочке.
@ValidatorConstraint()
class IsEven {
validate(value) {
return value % 2 === 0;
}
}
Для диагностики используется временное расширение:
Основной источник ошибок — неожиданный тип входного значения.
По умолчанию многие декораторы игнорируют undefined, но
не null.
@IsNotEmpty()
field;
Поведение:
undefined может пройти проверкуnull почти всегда вызывает ошибкуДля строгого контроля используется комбинация:
@IsDefined()
@IsNotEmpty()
field;
Ситуации, когда ошибок нет, но данные некорректны, возникают при:
Проверка выполняется через вывод итогового объекта после трансформации:
const instance = plainToInstance(User, input);
console.log(instance);
Если объект остаётся plain-структурой, валидация работает непредсказуемо.
Расширенный анализ достигается через рекурсивный обход
ValidationError:
function printErrors(errors) {
errors.forEach(err => {
console.log('Property:', err.property);
console.log('Constraints:', err.constraints);
if (err.children?.length) {
printErrors(err.children);
}
});
}
Такой подход позволяет выявлять ошибки на любом уровне вложенности, включая скрытые поля.
Некоторые комбинации создают противоречивую логику:
@IsOptional() + @IsNotEmpty()@Allow() + whitelist: true@ValidateNested() без @Type()Пример конфликтной ситуации:
@IsOptional()
@IsNotEmpty()
field;
Если поле отсутствует — пропускается. Если присутствует — не должно быть пустым. Такая комбинация часто приводит к ложным ожиданиям поведения.
Массивы требуют отдельной настройки:
import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
class Item {
name;
}
class Order {
@IsArray()
@ValidateNested({ each: true })
@Type(() => Item)
items;
}
Типичные ошибки:
each: true@Typeclass-validator не гарантирует строгий порядок
выполнения правил. Это важно при:
Поэтому валидаторы должны быть независимыми и не полагаться на последовательность.
Для поиска источника ошибки используется временное отключение правил:
Такой подход позволяет отделить проблемную логику от общей структуры модели.
При наследовании декораторы не всегда комбинируются интуитивно:
class Base {
@IsString()
id;
}
class User extends Base {
@IsEmail()
email;
}
Ошибки возникают при:
Особое внимание требуется при глубокой иерархии моделей.
Эффективный способ выявления проблемы — временное упрощение класса до одного поля:
class DebugModel {
@IsString()
test;
}
Если валидация работает корректно, проблема локализуется в исходной структуре, а не в библиотеке.
Иногда ошибки не видны из-за обработки на уровне приложения:
Проверка выполняется через прямой вывод результата
validate без промежуточных слоёв.