Сложные условные конструкции в Class-validator формируют основу для
моделей, где корректность одного поля зависит от состояния других полей,
внешнего контекста или бизнес-правил. В таких сценариях простых
декораторов типа @IsString() или @IsInt()
недостаточно — требуется управление логикой проверки через условия,
группы и пользовательские валидаторы.
Основной механизм условной валидации строится вокруг
@ValidateIf, а также пользовательских декораторов через
ValidatorConstraint.
@ValidateIf позволяет полностью включить или отключить
валидацию поля на основе функции-условия.
import { ValidateIf, IsString, IsNotEmpty } from 'class-validator';
class UserDto {
@ValidateIf(o => o.isCompany === true)
@IsString()
companyName;
@ValidateIf(o => o.isCompany === false)
@IsString()
firstName;
@ValidateIf(o => o.isCompany === false)
@IsString()
lastName;
isCompany;
}
Логика работы строится вокруг объекта валидации o,
который представляет текущий экземпляр класса.
Ключевая особенность: если ValidateIf
возвращает false, все последующие декораторы поля полностью
игнорируются.
Это делает возможной реализацию взаимоисключающих структур данных без необходимости писать сложную внешнюю логику.
Одной из наиболее распространённых задач является проверка зависимости между полями.
import { ValidateIf, Equals, IsString, MinLength } from 'class-validator';
class RegisterDto {
@IsString()
@MinLength(8)
password;
@ValidateIf(o => o.password !== undefined)
@Equals('password')
confirmPassword;
}
Однако такой подход ограничен, так как @Equals не
предназначен для сравнения с другим полем. Более корректный вариант —
пользовательский валидатор.
Class-validator предоставляет механизм создания кастомных правил
через ValidatorConstraint.
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
registerDecorator,
} from 'class-validator';
@ValidatorConstraint({ name: 'Match', async: false })
class MatchConstraint {
validate(value, args) {
const [relatedPropertyName] = args.constraints;
const relatedValue = args.object[relatedPropertyName];
return value === relatedValue;
}
defaultMessage(args) {
return `${args.property} must match ${args.constraints[0]}`;
}
}
function Match(property) {
return function (object, propertyName) {
registerDecorator({
target: object.constructor,
propertyName,
constraints: [property],
validator: MatchConstraint,
});
};
}
class RegisterDto {
password;
@Match('password')
confirmPassword;
}
Вместо жёсткой схемы «обязательно/необязательно» используется динамическая логика.
import { ValidateIf, IsNotEmpty, IsEmail } from 'class-validator';
class ContactDto {
@ValidateIf(o => o.preferredContact === 'email')
@IsEmail()
email;
@ValidateIf(o => o.preferredContact === 'phone')
@IsNotEmpty()
phone;
preferredContact;
}
Такая структура часто используется в формах с переключаемыми режимами ввода.
@IsOptional не является полноценным условным оператором,
но часто участвует в комбинированных конструкциях.
import { IsOptional, IsString, Length } from 'class-validator';
class UpdateUserDto {
@IsOptional()
@IsString()
@Length(3, 20)
username;
}
@IsOptional игнорирует undefined и
null@ValidateIf управляет всей цепочкой валидаторов@ValidateIf работает на уровне условия@IsOptional работает на уровне значенияКогда логика зависит от нескольких параметров, условия становятся составными.
class AccessDto {
@ValidateIf(o => o.role === 'admin' && o.isActive === true)
@IsString()
adminToken;
role;
isActive;
}
При усложнении логики вынос условия повышает читаемость:
function canAccessAdminFields(obj) {
return obj.role === 'admin' && obj.isActive === true;
}
class AccessDto {
@ValidateIf(canAccessAdminFields)
@IsString()
adminToken;
role;
isActive;
}
Class-validator поддерживает рекурсивную валидацию через
@ValidateNested, которая также может быть условной.
import { ValidateNested, ValidateIf } from 'class-validator';
import { Type } from 'class-transformer';
class Address {
street;
city;
}
class UserDto {
@ValidateIf(o => o.hasAddress === true)
@ValidateNested()
@Type(() => Address)
address;
hasAddress;
}
В таких структурах важно учитывать порядок преобразования через
class-transformer, так как условия могут зависеть от уже
преобразованных объектов.
Условие может включать асинхронную логику, если используется
async: true в кастомном валидаторе.
@ValidatorConstraint({ name: 'UniqueEmail', async: true })
class UniqueEmailConstraint {
async validate(email, args) {
const user = await fakeDatabaseFind(email);
return !user;
}
}
class UserDto {
@ValidateIf(o => o.allowRegistration === true)
@UniqueEmail()
email;
allowRegistration;
}
В реальных схемах часто встречаются многоуровневые зависимости:
class OrderDto {
@ValidateIf(o => o.type === 'delivery')
@IsNotEmpty()
address;
@ValidateIf(o => o.type === 'delivery' && o.priority === 'high')
@IsNotEmpty()
courierCode;
type;
priority;
}
Каждый уровень добавляет уточнение к предыдущему условию, формируя цепочку зависимостей.
ValidationArguments предоставляет доступ ко всему
объекту, что позволяет строить сложные правила.
class RangeConstraint {
validate(value, args) {
const obj = args.object;
return value >= obj.min && value <= obj.max;
}
}
class RangeDto {
min;
max;
@IsNumber()
rangeValue;
}
Такой подход часто используется для бизнес-правил, где поля образуют единое ограничение.
Validation groups позволяют разделять правила без
if.
import { IsString } from 'class-validator';
class UserDto {
@IsString({ groups: ['create'] })
password;
@IsString({ groups: ['update'] })
id;
}
Группы могут использоваться совместно с @ValidateIf,
формируя гибридную систему:
@ValidateIf(o => o.mode === 'strict')
@IsString({ groups: ['strict'] })
value;
Чрезмерно сложные условия внутри декоратора снижают предсказуемость:
@ValidateIf(o => o.a && o.b && (o.c || o.d) && o.e !== 'x')
Такие выражения затрудняют тестирование и сопровождение.
При использовании class-transformer условие может
срабатывать до преобразования типов, что приводит к неожиданным
результатам.
Комбинации могут давать неочевидное поведение:
@IsOptional пропускает undefined@ValidateIf отключает всю цепочкуИспользование обоих без необходимости приводит к дублированию логики.
При построении сложных DTO часто формируется архитектура, где:
@ValidateIfТакая композиция позволяет формировать гибкие схемы данных без вынесения логики за пределы модели.