@IsInstance

В библиотеке class-validator декоратор @IsInstance применяется для проверки того, что значение является экземпляром конкретного класса. Механизм основан на операторе instanceof, поэтому проверка выполняется на уровне прототипной цепочки, а не структуры объекта.

Ключевая особенность: валидатор не анализирует «похожесть» объекта на класс, а требует именно созданный через new экземпляр либо корректно восстановленный прототип.


Сигнатура и базовая форма использования

@IsInstance(ClassConstructor, validationOptions?)
  • ClassConstructor — класс, экземпляром которого должно быть значение
  • validationOptions — стандартные опции библиотеки (сообщение, группы и т.д.)

Базовый пример

import { IsInstance } from "class-validator";

class UserService {}

class Container {
  @IsInstance(UserService)
  service: UserService;
}

В данном случае поле service считается валидным только если оно создано как:

container.service = new UserService();

Любой другой объект, даже с идентичной структурой, будет отклонён.


Принцип работы instanceof

Проверка внутри декоратора эквивалентна:

value instanceof UserService

Это означает, что валидатор опирается на:

  • цепочку прототипов
  • ссылку на конструктор
  • оригинальный класс в runtime

Важные ограничения механизма

1. Потеря прототипа

После сериализации (например, JSON → объект) экземпляр перестаёт быть валидным:

const obj = JSON.parse('{"name":"test"}');

obj instanceof UserService; // false

Даже если структура совпадает, классовая информация отсутствует.


2. Разные экземпляры класса из разных модулей

В монорепозиториях или при дублировании зависимостей:

import { Service } from "./service";

const a = new Service();
const b = require("./service").Service;

a instanceof b; // может быть false при дублях модулей

Это типичная проблема при:

  • дублированных node_modules
  • linked-пакетах
  • некорректной сборке бандла

3. Не работает с интерфейсами

Интерфейсы TypeScript существуют только на этапе компиляции:

interface IUser {}

@IsInstance(IUser) // невозможно

На runtime интерфейса не существует, поэтому проверка невозможна.


Использование с наследованием

@IsInstance учитывает наследование через instanceof.

class Animal {}

class Dog extends Animal {}

class Zoo {
  @IsInstance(Animal)
  creature: Animal;
}

const z = new Zoo();
z.creature = new Dog(); // валидно

Экземпляр подкласса проходит проверку родительского класса.


Поведение с кастомными классами

class Logger {
  log(msg: string) {}
}

class App {
  @IsInstance(Logger)
  logger: Logger;
}

Требуется именно new Logger(), а не объект с аналогичным методом log.


Совместное использование с преобразованием данных

В реальных приложениях входящие данные часто требуют восстановления экземпляров классов. Обычно используется связка с class-transformer:

import { Type } from "class-transformer";
import { IsInstance } from "class-validator";

class Logger {}

class App {
  @Type(() => Logger)
  @IsInstance(Logger)
  logger: Logger;
}

Здесь:

  • @Type создаёт реальный экземпляр
  • @IsInstance подтверждает его тип

Без @Type объект останется plain object и проверка провалится.


Проверка массивов экземпляров

Сам @IsInstance не предназначен для массивов. В таком случае требуется комбинация:

import { ValidateNested, IsArray, IsInstance } from "class-validator";
import { Type } from "class-transformer";

class Plugin {}

class System {
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => Plugin)
  @IsInstance(Plugin, { each: true })
  plugins: Plugin[];
}

Однако важно учитывать: IsInstance с each: true применяется не во всех версиях одинаково, поэтому чаще достаточно ValidateNested.


Опции validationOptions

Поддерживаются стандартные параметры:

@IsInstance(Logger, {
  message: "logger должен быть экземпляром Logger"
})
logger: Logger;

Часто применяемые опции:

  • message — пользовательское сообщение
  • groups — группировка валидации
  • always — выполнение вне зависимости от условий

Отличие от структурной валидации

@IsInstance не заменяет проверки формы объекта.

Сравнение:

Подход Проверка
IsInstance принадлежность к классу
IsString / IsNumber примитивные типы
ValidateNested структура объекта

Пример конфликта:

class A {
  x: number;
}

const obj = { x: 10 };

obj instanceof A; // false

Структурно объект подходит, но для IsInstance он невалиден.


Частые ошибки при использовании

Передача фабричной функции вместо класса

@IsInstance(() => Logger) // неверно

Должен передаваться сам конструктор:

@IsInstance(Logger)

Использование после JSON.parse

const data = JSON.parse(input);

@IsInstance(User)
user: User;

После парсинга объект теряет прототип.


Смешивание с plain objects

user = { name: "John" };

Даже если логически объект соответствует классу, проверка провалится.


Поведение в runtime-валидации

При вызове:

validate(instance)

или

validateOrReject(instance)

алгоритм:

  1. Берёт значение поля
  2. Проверяет instanceof ClassConstructor
  3. При несоответствии добавляет ValidationError
  4. Возвращает список ошибок

Использование в архитектуре приложений

Декоратор часто применяется в слоях:

  • DTO (Data Transfer Objects)
  • сервисные контейнеры
  • dependency-like структуры
  • конфигурационные классы

Пример DTO:

class DatabaseClient {}

class Config {
  @IsInstance(DatabaseClient)
  db: DatabaseClient;
}

Это гарантирует, что конфигурация содержит именно подготовленный объект, а не случайный литерал.


Особенности поведения при компиляции и бандлинге

В сложных сборках возможны ситуации:

  • дублирование классов после tree-shaking
  • различие экземпляров из-за разных контекстов загрузки
  • потеря identity конструктора

Это приводит к тому, что instanceof становится ложным даже при визуально идентичных классах.


Итоговая логика применения

@IsInstance используется исключительно тогда, когда важна не структура данных, а факт принадлежности к конкретному классу в runtime. Это делает его узкоспециализированным инструментом, который дополняет структурные валидаторы, но не заменяет их.