@IsDefined

Декоратор @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

Декоратор считает значение корректным, если оно:

  • не равно undefined
  • не равно null

Допустимыми считаются:

''
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.

Пример без @IsDefined

import { 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);
}

Ошибок не будет, потому что отсутствующие поля пропускаются.


Пример с @IsDefined

import {
  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

Наиболее частая область применения — 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

Поведение при undefined

const dto = {
  title: undefined
};

Результат аналогичен:

title should not be null or undefined

Проверка boolean-полей

@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 обязательно'
}

Использование функции в message

class UserDto {
  @IsDefined({
    message: args => {
      return `Свойство ${args.property} отсутствует`;
    }
  })
  name: string;
}

Работа с API

Очень часто @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

В NestJS декоратор применяется практически во всех DTO.

import {
  IsDefined,
  IsString
} from 'class-validator';

export class CreatePostDto {
  @IsDefined()
  @IsString()
  title: string;
}

Совместно с ValidationPipe это обеспечивает автоматическую проверку запросов.


Отличие от проверки через TypeScript

Типы 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

Подходящие сценарии:

  • обязательные поля API
  • критически важные параметры
  • проверка boolean-значений
  • проверка чисел со значением 0
  • условно обязательные поля
  • строгая валидация DTO

Когда @IsDefined не нужен

Не рекомендуется использовать декоратор:

  • для необязательных полей
  • вместе с @IsOptional
  • если достаточно @IsNotEmpty
  • при полной опциональности DTO

Конфликт с @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

валидация будет успешной, потому что поле определено.