Avatar ·

Проверка типа интерфейса в TypeScript: как определить соответствие объекта интерфейсу?

Привет, коллеги! Я начал изучать TypeScript и столкнулся с проблемой, которая кажется элементарной, но я не могу найти правильное решение. У меня есть интерфейс: ```typescript interface User { name: string; age: number; email: string; } ``` Я получаю данные из API в виде `any` или `unknown`. Мне нужно проверить, соответствует ли полученный объект интерфейсу `User`, прежде чем использовать его поля. В JavaScript я бы написал `typeof obj.name === 'string'` и так далее, но в TypeScript я хочу сделать это элегантно, используя возможности языка. Я пробовал писать `if (obj instanceof User)`, но TypeScript ругается, что `User` — это интерфейс, а не класс. Пробовал использовать `typeof obj === 'User'` — тоже не работает. Гуглил и нашел упоминания про type guards и пользовательские функции, но не до конца понял, как правильно сделать. Подскажите, как в TypeScript проверить, что объект соответствует интерфейсу? Важно, чтобы проверка работала и на этапе компиляции (TypeScript не ругался), и в рантайме (если данные пришли с сервера с неверной структурой). Пример кода приветствуется. <<>>

Проблема: интерфейсы TypeScript существуют только в compile-time

Интерфейсы TypeScript не имеют представления в рантайме — они полностью стираются при компиляции в JavaScript. Поэтому вы не можете использовать instanceof или typeof для проверки соответствия интерфейсу. Решение — написать пользовательскую функцию-проверку (type guard), которая выполняет ручную валидацию структуры объекта.

Решение: пользовательский type guard с is-предикатом

Создайте функцию, которая принимает unknown и возвращает obj is User. Внутри проверьте каждое поле:

interface User {
  name: string;
  age: number;
  email: string;
}

function isUser(obj: unknown): obj is User {
  if (!obj || typeof obj !== 'object') return false;
  
  const user = obj as Record<string, unknown>;
  return (
    typeof user.name === 'string' &&
    typeof user.age === 'number' &&
    typeof user.email === 'string'
  );
}

// Использование:
const data: unknown = JSON.parse('{"name":"Alice","age":30,"email":"alice@example.com"}');

if (isUser(data)) {
  // TypeScript знает, что data — User
  console.log(data.name.toUpperCase()); // OK
}

Расширенный подход: использование библиотеки для валидации

Если у вас много интерфейсов, ручное написание проверок становится утомительным. Используйте библиотеку zod или io-ts. Пример с zod:

import { z } from 'zod';

const UserSchema = z.object({
  name: z.string(),
  age: z.number(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

// Проверка:
const result = UserSchema.safeParse(data);
if (result.success) {
  const user: User = result.data; // строго типизирован
} else {
  console.error(result.error);
}

Важные моменты

  • Проверка на null/undefined: всегда начинайте с if (!obj || typeof obj !== 'object').
  • Глубокие объекты: для вложенных структур рекурсивно проверяйте каждое поле.
  • Необязательные поля: используйте if (field !== undefined && typeof field !== ...).
  • Производительность: для больших данных рассмотрите схемы валидации (zod, ajv).

Этот подход гарантирует безопасность на этапе компиляции и в рантайме. TypeScript не будет ругаться, а если данные придут с ошибкой — вы получите понятное сообщение.

<<>>

Альтернатива: использование класса с instanceof

Если вы не хотите писать сложные проверки вручную, можно заменить интерфейс на класс. У классов есть представление в рантайме, и можно использовать instanceof:

class User {
  constructor(
    public name: string,
    public age: number,
    public email: string
  ) {}
}

function parseUser(obj: unknown): User | null {
  if (!obj || typeof obj !== 'object') return null;
  const { name, age, email } = obj as any;
  if (typeof name === 'string' && typeof age === 'number' && typeof email === 'string') {
    return new User(name, age, email);
  }
  return null;
}

const data = JSON.parse('{"name":"Bob","age":25,"email":"bob@test.com"}');
const user = parseUser(data);
if (user instanceof User) {
  console.log(user.name); // безопасно
}

Недостатки:

  • Классы сложнее интерфейсов (нужно писать конструктор).
  • Потеря структурной типизации — классы номинативны.
  • Для JSON-данных всё равно нужна ручная проверка полей.

Этот подход подходит, если вы контролируете создание объектов и хотите использовать instanceof для проверки типа в рантайме. Для чистых данных из API лучше использовать первый вариант с type guard или библиотеку валидации.