Avatar ·

Как получить ключи интерфейса TypeScript в виде массива строк?

Всем привет! Я начал изучать TypeScript и столкнулся с проблемой, которую никак не могу решить. У меня есть интерфейс, описывающий структуру объекта, и мне нужно получить список его ключей в виде массива строк для дальнейшего использования в коде (например, для валидации или генерации форм). Вот пример моего интерфейса: ```typescript interface User { name: string; age: number; email: string; } ``` Я хочу получить массив `['name', 'age', 'email']` автоматически, на основе этого интерфейса. Пробовал использовать `Object.keys()`, но TypeScript ругается, что я передаю не объект, а тип. Пробовал разные варианты с `keyof`, но получаю только объединение строк, а не массив. Подскажите, как это сделать правильно? Нужно именно решение на TypeScript, чтобы типы сохранялись. Буду очень благодарен за примеры кода! <<>>

Проблема: ключи интерфейса недоступны в рантайме

Главная сложность в том, что интерфейсы TypeScript существуют только на этапе компиляции. В скомпилированном JavaScript от них не остаётся и следа. Поэтому получить массив ключей напрямую из интерфейса невозможно — нужно использовать объект, реализующий этот интерфейс, либо применять специальные техники.

Решение 1: Через объект с типом (простой способ)

Самый надёжный способ — создать объект, который соответствует интерфейсу, и взять его ключи через Object.keys(). TypeScript позволит это сделать, если правильно указать тип:

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

// Создаём объект с явным указанием типа
const user: User = {
  name: 'Alice',
  age: 30,
  email: 'alice@example.com'
};

// Получаем массив ключей
const keys = Object.keys(user) as Array<keyof User>;
// keys: ("name" | "age" | "email")[]
console.log(keys); // ['name', 'age', 'email']

Важно: Используйте as Array<keyof User>, чтобы сохранить тип ключей. Без этого TypeScript вернёт string[].

Решение 2: Функция-утилита с проверкой типов

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

function getKeys<T extends object>(obj: T): Array<keyof T> {
  return Object.keys(obj) as Array<keyof T>;
}

// Использование
const userKeys = getKeys({ name: 'Bob', age: 25 });
// userKeys: ("name" | "age")[]

Решение 3: Через класс (для динамических данных)

Если данные приходят из API и вы не можете создать объект заранее, используйте класс с конструктором:

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

const user = new UserEntity('Charlie', 35, 'charlie@mail.com');
const keys = Object.keys(user) as Array<keyof UserEntity>;
// keys: ("name" | "age" | "email")[]

Решение 4: Компиляторный хак (только для продвинутых)

Существует недокументированный способ использовать ts-transformer-keys или typescript-json-schema, но это требует настройки сборки. Не рекомендую новичкам.

Что делать, если объекта нет?

Если вы работаете с типами, а не с объектами (например, в generic-функции), можно использовать тип-маппер:

type KeysArray<T> = Array<keyof T>;

// Пример использования
function processUser<T extends User>(data: T) {
  const keys: KeysArray<T> = Object.keys(data) as KeysArray<T>;
  // Работаем с keys
}

Важное предостережение

Помните: Object.keys() возвращает только собственные (не унаследованные) ключи объекта. Если интерфейс наследуется, метод может не вернуть все ключи родительского типа. Используйте for...in с проверкой hasOwnProperty для полного обхода.

Надеюсь, это поможет! Выбирайте способ в зависимости от вашей задачи. Для большинства случаев достаточно первого варианта с явным созданием объекта.

<<>>

Альтернативный подход: Использование mapped types

Если вам нужно получить ключи интерфейса без создания объекта, можно применить трюк с mapped types и пустым объектом. Этот способ подходит, когда у вас есть только тип, но нет данных:

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

// Создаём тип-заглушку
type UserKeys = keyof User;
// UserKeys = "name" | "age" | "email"

// Приводим к массиву через пустой объект
const keys = Object.keys({} as User) as Array<UserKeys>;
// Но это вернёт пустой массив! Не подходит.

Этот метод не сработает, так как Object.keys на пустом объекте вернёт []. Поэтому для реального получения ключей всё равно нужен объект с данными.

Дополнение: Использование декораторов (экспериментально)

Если вы используете экспериментальные декораторы TypeScript, можно создать декоратор, который записывает ключи в статическое поле:

function RecordKeys<T>(target: T) {
  const keys: Array<keyof T> = Object.keys(target.prototype) as any;
  target.keys = keys;
}

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

console.log(User.keys); // ['name', 'age'] (требует настройки)

Но это сложный и нестандартный путь. Лучше придерживаться первого ответа.