Декоратор @ValidateIf в библиотеке
class-validator позволяет управлять условной валидацией
свойств объекта. Он используется для того, чтобы запускать или
пропускать проверки других валидаторов в зависимости от результата
пользовательской функции. Это ключевой инструмент для построения
динамических схем валидации, где правила зависят от состояния объекта,
входных данных или внешних условий.
Основная идея заключается в том, что @ValidateIf не
выполняет валидацию сам по себе, а определяет, будут ли
применены последующие декораторы к полю.
@ValidateIf принимает функцию-предикат, которая получает
текущий объект и значение поля. Возвращаемое значение определяет, нужно
ли применять остальные валидаторы:
true — валидация продолжаетсяfalse — все последующие валидаторы для этого свойства
игнорируютсяСигнатура:
ValidateIf(condition: (object: any, value: any) => boolean)
Важно учитывать порядок выполнения: @ValidateIf должен
находиться выше других декораторов в цепочке, так как он влияет на их
выполнение.
import { ValidateIf, IsNotEmpty, IsEmail } from 'class-validator';
class User {
@ValidateIf(o => o.isEmailRequired === true)
@IsNotEmpty()
@IsEmail()
email: string;
isEmailRequired: boolean;
}
В этом примере поле email проверяется только в случае,
если isEmailRequired равно true. Если условие
ложно, IsNotEmpty и IsEmail не
выполняются.
Функция внутри @ValidateIf получает два аргумента:
object — текущий экземпляр классаvalue — значение текущего свойства@ValidateIf((obj, value) => obj.mode === 'strict' && value !== undefined)
Использование value полезно для проверки наличия данных,
а object — для логики, зависящей от других полей.
Один из распространённых сценариев — динамическое управление обязательностью поля.
class Payment {
@ValidateIf(o => o.method === 'card')
@IsNotEmpty()
cardNumber: string;
method: string;
}
Здесь cardNumber становится обязательным только при
выборе метода оплаты card.
Если method !== 'card', поле полностью исключается из
проверки.
@ValidateIf влияет только на выполнение следующих
декораторов. Сам по себе он не изменяет значение поля и не вмешивается в
его преобразование.
Пример цепочки:
@ValidateIf(o => o.enabled)
@IsString()
@Length(5, 20)
value: string;
Если enabled === false, то ни IsString, ни
Length не выполняются.
Если значение поля отсутствует (undefined), поведение
зависит от условия:
@ValidateIf((o, v) => v !== undefined)
@IsNotEmpty()
field: string;
В этом случае валидация не будет выполняться для
undefined. Однако это не делает поле автоматически валидным
— оно просто исключается из проверки.
@ValidateIf может работать и с вложенными структурами,
если они корректно типизированы.
class Address {
@ValidateIf(o => o.required === true)
@IsNotEmpty()
street: string;
required: boolean;
}
class User {
address: Address;
}
Однако условие всегда оценивается на уровне текущего объекта, поэтому для вложенных классов важно правильно управлять контекстом и структурой данных.
Когда одно из нескольких полей должно быть валидным:
class Search {
@ValidateIf(o => !o.query)
@IsNotEmpty()
id: string;
@ValidateIf(o => !o.id)
@IsNotEmpty()
query: string;
}
Так обеспечивается логика «либо ID, либо строковый запрос».
class Config {
@ValidateIf(o => o.env === 'production')
@IsNotEmpty()
apiKey: string;
env: string;
}
Валидация активируется только в production-среде.
class Profile {
@ValidateIf(o => o.subscribe === true)
@IsEmail()
email: string;
subscribe: boolean;
}
Поле email становится актуальным только при включённой подписке.
Функция условия выполняется во время валидации, а не при создании
объекта. Это означает, что изменение свойств объекта до вызова
validate() влияет на результат.
@ValidateIf не зависит от типа данных или порядка других
валидаторов, но строго влияет на их выполнение.
Если на одном поле несколько @ValidateIf, применяется
только ближайший к полю в цепочке.
@ValidateIf(o => o.a)
@ValidateIf(o => o.b)
@IsString()
value: string;
Фактически учитывается только первый декоратор, ближайший к свойству.
@ValidateIf(o => o.a && o.b || o.c && !o.d)
Такие выражения ухудшают читаемость и затрудняют диагностику ошибок валидации.
Если условие зависит от полей, которые изменяются в процессе обработки, результат валидации может становиться непредсказуемым.
Функция условия должна быть чистой. Любые изменения состояния объекта внутри неё приводят к нестабильной работе.
@ValidateIf часто используется совместно с:
@IsNotEmpty@IsEmail@Length@IsOptional (в некоторых сценариях альтернативен, но не
эквивалентен)Важно различать:
@IsOptional() пропускает null и
undefined@ValidateIf() управляет всей цепочкой валидаторов на
основе условия@ValidateIf и @IsOptional@IsOptional работает по принципу:
@ValidateIf:
Пример различия:
@IsOptional()
@IsEmail()
email: string;
@ValidateIf(o => o.mode === 'email')
@IsEmail()
email: string;
Во втором случае логика не зависит от наличия значения, а от внешнего состояния.
При проектировании схем с @ValidateIf важно
учитывать:
@ValidateIf не влияет на class-transformer
и не изменяет структуру объекта. Он работает только на этапе валидации,
после того как объект уже создан и преобразован.
Это позволяет разделять ответственность:
class Group {
@ValidateIf(o => o.members?.length > 0)
@IsNotEmpty({ each: true })
members: string[];
}
Проверка массива выполняется только если он содержит элементы.
class Order {
@ValidateIf(o => o.payment?.status === 'pending')
@IsNotEmpty()
transactionId: string;
payment: {
status: string;
};
}
Такой подход позволяет учитывать состояние вложенных объектов.
Функция @ValidateIf должна быть синхронной. Асинхронные
проверки не поддерживаются напрямую. Любая попытка использовать промисы
или async-функции приведёт к некорректной работе
валидации.