В библиотеке 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();
Любой другой объект, даже с идентичной структурой, будет отклонён.
Проверка внутри декоратора эквивалентна:
value instanceof UserService
Это означает, что валидатор опирается на:
После сериализации (например, JSON → объект) экземпляр перестаёт быть валидным:
const obj = JSON.parse('{"name":"test"}');
obj instanceof UserService; // false
Даже если структура совпадает, классовая информация отсутствует.
В монорепозиториях или при дублировании зависимостей:
import { Service } from "./service";
const a = new Service();
const b = require("./service").Service;
a instanceof b; // может быть false при дублях модулей
Это типичная проблема при:
Интерфейсы 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.
Поддерживаются стандартные параметры:
@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)
const data = JSON.parse(input);
@IsInstance(User)
user: User;
После парсинга объект теряет прототип.
user = { name: "John" };
Даже если логически объект соответствует классу, проверка провалится.
При вызове:
validate(instance)
или
validateOrReject(instance)
алгоритм:
instanceof ClassConstructorДекоратор часто применяется в слоях:
Пример DTO:
class DatabaseClient {}
class Config {
@IsInstance(DatabaseClient)
db: DatabaseClient;
}
Это гарантирует, что конфигурация содержит именно подготовленный объект, а не случайный литерал.
В сложных сборках возможны ситуации:
Это приводит к тому, что instanceof становится ложным
даже при визуально идентичных классах.
@IsInstance используется исключительно тогда, когда
важна не структура данных, а факт принадлежности к конкретному классу в
runtime. Это делает его узкоспециализированным инструментом, который
дополняет структурные валидаторы, но не заменяет их.