Декоратор @IsDefined из библиотеки class-validator
используется для проверки того, что значение свойства существует и не
равно undefined или null.
Главная особенность этого декоратора заключается в том, что он
игнорирует глобальную настройку skipMissingProperties и
всегда требует наличие значения.
Для работы необходимы пакеты:
npm install class-validator class-transformer
Также требуется включить поддержку декораторов в
tsconfig.json:
{
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
import { IsDefined, validate } from 'class-validator';
class UserDto {
@IsDefined()
name: string;
}
async function run() {
const dto = new UserDto();
const errors = await validate(dto);
console.log(errors);
}
run();
Результат:
[
ValidationError {
property: 'name',
constraints: {
isDefined: 'name should not be null or undefined'
}
}
]
Поле name отсутствует, поэтому валидация завершилась
ошибкой.
@IsDefinedДекоратор считает значение корректным, если оно:
undefinednullДопустимыми считаются:
''
0
false
[]
{}
Пример:
import { IsDefined } from 'class-validator';
class ExampleDto {
@IsDefined()
value: any;
}
value = '';
value = 0;
value = false;
value = [];
value = undefined;
value = null;
@IsNotEmptyОчень распространённая ошибка — путать @IsDefined и
@IsNotEmpty.
@IsDefinedПроверяет только наличие значения.
@IsDefined()
title: string;
Допустимо:
title = '';
@IsNotEmptyПроверяет, что значение не пустое.
@IsNotEmpty()
title: string;
Недопустимо:
title = '';
| Значение | @IsDefined |
@IsNotEmpty |
|---|---|---|
undefined |
Ошибка | Ошибка |
null |
Ошибка | Ошибка |
'' |
OK | Ошибка |
'text' |
OK | OK |
0 |
OK | OK |
false |
OK | OK |
skipMissingPropertiesОдно из важнейших свойств @IsDefined — игнорирование
настройки skipMissingProperties.
@IsDefinedimport { IsString, validate } from 'class-validator';
class UpdateUserDto {
@IsString()
name: string;
}
async function run() {
const dto = new UpdateUserDto();
const errors = await validate(dto, {
skipMissingProperties: true
});
console.log(errors);
}
Ошибок не будет, потому что отсутствующие поля пропускаются.
@IsDefinedimport {
IsDefined,
IsString,
validate
} from 'class-validator';
class UpdateUserDto {
@IsDefined()
@IsString()
name: string;
}
async function run() {
const dto = new UpdateUserDto();
const errors = await validate(dto, {
skipMissingProperties: true
});
console.log(errors);
}
Теперь ошибка появится даже при включённом
skipMissingProperties.
Это делает @IsDefined особенно полезным при частичном
обновлении объектов.
Наиболее частая область применения — DTO-классы.
import {
IsDefined,
IsEmail,
IsString
} from 'class-validator';
class RegisterDto {
@IsDefined()
@IsString()
username: string;
@IsDefined()
@IsEmail()
email: string;
@IsDefined()
@IsString()
password: string;
}
Если клиент не отправит одно из полей, валидация завершится ошибкой.
nullВажно понимать, что null также считается отсутствующим
значением.
class ProductDto {
@IsDefined()
title: string;
}
const dto = {
title: null
};
Результат:
title should not be null or undefined
undefinedconst dto = {
title: undefined
};
Результат аналогичен:
title should not be null or undefined
@IsDefined особенно полезен для булевых значений.
Без него часто возникают ошибки из-за false.
if (!dto.isAdmin) {
throw new Error('Field is required');
}
Проблема:
isAdmin = false
false интерпретируется как отсутствие значения.
import {
IsBoolean,
IsDefined
} from 'class-validator';
class UserDto {
@IsDefined()
@IsBoolean()
isAdmin: boolean;
}
Теперь:
isAdmin = false
считается валидным значением.
Аналогичная ситуация возникает с числом 0.
class ProductDto {
@IsDefined()
price: number;
}
Корректно:
price = 0;
Некорректно:
price = undefined;
@ValidateIf@IsDefined можно комбинировать с условной
валидацией.
import {
IsDefined,
ValidateIf
} from 'class-validator';
class PaymentDto {
paymentType: string;
@ValidateIf(o => o.paymentType === 'card')
@IsDefined()
cardNumber: string;
}
Теперь поле cardNumber обязательно только при оплате
картой.
import {
IsDefined,
ValidateNested
} from 'class-validator';
class AddressDto {
@IsDefined()
city: string;
}
class UserDto {
@IsDefined()
@ValidateNested()
address: AddressDto;
}
Если address отсутствует:
{
"address": null
}
будет ошибка.
import {
IsArray,
IsDefined
} from 'class-validator';
class TagsDto {
@IsDefined()
@IsArray()
tags: string[];
}
Корректно:
tags = [];
Некорректно:
tags = undefined;
Пустой массив считается существующим значением.
import { IsDefined } from 'class-validator';
class UserDto {
@IsDefined({
message: 'Поле name обязательно'
})
name: string;
}
Результат:
{
isDefined: 'Поле name обязательно'
}
messageclass UserDto {
@IsDefined({
message: args => {
return `Свойство ${args.property} отсутствует`;
}
})
name: string;
}
Очень часто @IsDefined применяется в REST API.
Пример входящего JSON:
{
"email": "admin@test.com"
}
DTO:
class CreateUserDto {
@IsDefined()
username: string;
@IsDefined()
email: string;
}
Ошибка:
{
"username": [
"username should not be null or undefined"
]
}
В NestJS декоратор применяется практически во всех DTO.
import {
IsDefined,
IsString
} from 'class-validator';
export class CreatePostDto {
@IsDefined()
@IsString()
title: string;
}
Совместно с ValidationPipe это обеспечивает
автоматическую проверку запросов.
Типы TypeScript работают только во время компиляции.
class UserDto {
name: string;
}
Даже если поле обязательно по типу:
const dto = {} as UserDto;
объект всё равно может прийти без name во время
выполнения.
@IsDefined решает именно runtime-задачу.
PartialTypeВ NestJS существует PartialType, который делает все поля
необязательными.
export class UpdateUserDto extends PartialType(CreateUserDto) {}
Если внутри CreateUserDto используется
@IsDefined, поле всё равно останется обязательным.
Пример:
class CreateUserDto {
@IsDefined()
name: string;
}
После:
class UpdateUserDto extends PartialType(CreateUserDto) {}
поле name продолжит требоваться.
Это связано с тем, что @IsDefined игнорирует
skipMissingProperties.
@IsDefinedПодходящие сценарии:
0@IsDefined не
нуженНе рекомендуется использовать декоратор:
@IsOptional@IsNotEmpty@IsOptionalСледующая комбинация противоречива:
class UserDto {
@IsOptional()
@IsDefined()
name: string;
}
@IsOptional разрешает отсутствие поля, а
@IsDefined запрещает.
Такая конструкция создаёт неоднозначное поведение и должна избегаться.
Упрощённо декоратор выполняет проверку:
value !== undefined && value !== null
Именно поэтому значения:
0
false
''
проходят валидацию.
import {
IsBoolean,
IsDefined,
IsEmail,
IsInt,
IsString,
Min
} from 'class-validator';
class CreateEmployeeDto {
@IsDefined()
@IsString()
firstName: string;
@IsDefined()
@IsString()
lastName: string;
@IsDefined()
@IsEmail()
email: string;
@IsDefined()
@IsInt()
@Min(18)
age: number;
@IsDefined()
@IsBoolean()
isActive: boolean;
}
Пример корректного объекта:
{
"firstName": "Alex",
"lastName": "Smith",
"email": "alex@test.com",
"age": 18,
"isActive": false
}
Даже при:
"isActive": false
валидация будет успешной, потому что поле определено.