Проверка типа по интерфейсу в TypeScript: как определить, соответствует ли объект интерфейсу?
Проблема проверки интерфейсов во время выполнения
В TypeScript интерфейсы существуют только на этапе компиляции и полностью стираются в рантайме. Поэтому такие конструкции, как instanceof, typeof или data.constructor === User, не работают. Единственный способ проверить соответствие объекта интерфейсу — написать пользовательский защитник типа (type guard) или использовать библиотеки для валидации.
Решение 1: Пользовательский защитник типа (type guard)
Самый простой и прозрачный способ — написать функцию, которая проверяет наличие всех необходимых полей и их типы:
interface User {
id: number;
name: string;
email: string;
}
// Пользовательский защитник типа
function isUser(obj: unknown): obj is User {
if (typeof obj !== 'object' || obj === null) return false;
const user = obj as Record<string, unknown>;
return (
typeof user.id === 'number' &&
typeof user.name === 'string' &&
typeof user.email === 'string'
);
}
// Использование
function processData(data: unknown) {
if (isUser(data)) {
// TypeScript знает, что data — это User
console.log(data.name); // ✅ Без ошибок
console.log(data.id); // ✅
}
}
Преимущества:
- Полный контроль над логикой проверки
- Не требует внешних зависимостей
- TypeScript корректно сужает тип внутри блока
if
Недостатки:
- Ручное написание для каждого интерфейса
- Легко ошибиться при большом количестве полей
Решение 2: Использование библиотеки Zod
Для сложных проектов лучше использовать специализированные библиотеки валидации, такие как Zod:
import { z } from 'zod';
// Определяем схему
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email()
});
// Тип, производный от схемы
type User = z.infer<typeof UserSchema>;
// Использование
function processData(data: unknown) {
const result = UserSchema.safeParse(data);
if (result.success) {
// result.data — это User
console.log(result.data.name);
} else {
console.error(result.error);
}
}
Преимущества Zod:
- Автоматическая генерация типов из схемы
- Поддержка вложенных объектов, массивов, опциональных полей
- Детальные сообщения об ошибках валидации
Решение 3: Комбинированный подход для больших интерфейсов
Если вы не хотите использовать внешние библиотеки, но у вас много интерфейсов, можно написать утилиту для автоматической генерации защитников типов:
// Утилита для создания type guard
function createTypeGuard<T>(fields: (keyof T)[]) {
return (obj: unknown): obj is T => {
if (typeof obj !== 'object' || obj === null) return false;
const record = obj as Record<string, unknown>;
return fields.every(field => field in record);
};
}
// Использование
const isUser = createTypeGuard<User>(['id', 'name', 'email']);
function processData(data: unknown) {
if (isUser(data)) {
console.log(data.name); // ✅
}
}
Важно: Этот подход проверяет только наличие полей, но не их типы. Для строгой проверки типов полей всё равно придётся писать более сложную логику.
Рекомендация
Для небольших проектов с 2-3 интерфейсами используйте ручные защитники типов. Для крупных проектов с множеством схем данных — Zod или аналоги (Yup, io-ts). Главное — никогда не полагайтесь на instanceof или typeof для проверки интерфейсов, так как это невозможно в принципе.
Альтернативный подход: декораторы классов
Если вы готовы перейти от интерфейсов к классам, можно использовать декораторы для автоматической проверки типов. Это даёт возможность использовать instanceof и рефлексию:
import 'reflect-metadata';
function validate(target: any, key: string) {
const type = Reflect.getMetadata('design:type', target, key);
console.log(`Поле ${key} должно быть ${type.name}`);
}
class User {
@validate
id!: number;
@validate
name!: string;
@validate
email!: string;
}
// Теперь можно использовать instanceof
function processData(data: unknown) {
if (data instanceof User) {
console.log(data.name); // ✅
}
}
Почему это может быть удобно?
- Сохраняется нативная проверка через
instanceof - Можно добавлять методы и бизнес-логику прямо в класс
- Подходит для проектов, где уже используется ООП
Недостатки
- Требует настройки
experimentalDecoratorsв tsconfig.json - Классы — это значения, они занимают память в рантайме
- Меньшая гибкость по сравнению с интерфейсами
Этот подход подойдёт, если вы строите приложение на классах и хотите сохранить строгую типизацию с возможностью рантайм-проверки. Для чисто типографических решений (DTO, ответы API) лучше всё же использовать интерфейсы с защитниками типов или Zod.