Как получить имена (ключи) значений перечисления (enum) в TypeScript?
Почему Object.keys() возвращает числа?
Это не баг, а особенность работы TypeScript/JavaScript с числовыми перечислениями. При компиляции TypeScript генерирует так называемую обратную маппинг-таблицу (reverse mapping). Для каждого числового значения создается пара: ключ → значение и значение → ключ. Именно поэтому в массиве появляются как строковые ключи ("Red", "Green"), так и числовые ("1", "2").
Для строковых перечислений (где значения — строки) такой проблемы нет, так как обратная маппинг-таблица не создается. Но для числовых — это нужно учитывать.
Решение: фильтрация числовых ключей
Самый простой и надежный способ — отфильтровать только строковые ключи. Для этого можно использовать isNaN() или проверку на тип:
enum Color {
Red = 1,
Green = 2,
Blue = 4
}
// Способ 1: через isNaN
const colorNames = Object.keys(Color).filter(key => isNaN(Number(key)));
console.log(colorNames); // ["Red", "Green", "Blue"]
// Способ 2: через typeof (более явный)
const colorNames2 = Object.keys(Color).filter(key => typeof Color[key as any] === 'number');
console.log(colorNames2); // ["Red", "Green", "Blue"]
Универсальная функция для любых enum
Чтобы не писать фильтрацию каждый раз, создайте вспомогательную функцию:
function getEnumNames<T extends Record<string, string | number>>(enumObj: T): string[] {
return Object.keys(enumObj).filter(key => {
// Проверка: если значение — число, то это ключ, иначе — обратная маппинг-запись
return isNaN(Number(key));
});
}
// Пример использования:
enum Status {
Active = 'active',
Inactive = 'inactive',
Pending = 0,
Done = 1
}
console.log(getEnumNames(Color)); // ["Red", "Green", "Blue"]
console.log(getEnumNames(Status)); // ["Active", "Inactive", "Pending", "Done"]
Для строковых enum — всё проще
Если вы используете только строковые значения, можете просто брать Object.keys() без фильтрации:
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
console.log(Object.keys(Direction)); // ["Up", "Down", "Left", "Right"]
Альтернативный подход: через for...in
Можно также использовать цикл for...in, но он менее производителен и требует дополнительной проверки:
function getEnumNamesForIn(enumObj: any): string[] {
const names: string[] = [];
for (const key in enumObj) {
if (isNaN(Number(key))) {
names.push(key);
}
}
return names;
}
Важные замечания
- const enum — компилируются в инлайн-значения и не имеют объекта во время выполнения. Для них
Object.keys()не сработает вообще. Используйте обычные enum, если нужен доступ к именам. - Гетерогенные enum (смесь чисел и строк) — функция с фильтрацией
isNaNбудет работать корректно. - Производительность — фильтрация массива — операция O(n), но для перечислений с десятками элементов это не критично.
Надеюсь, это помогло разобраться! Если остались вопросы — пишите.
<<Использование Object.values() и вспомогательного типа
Если вам нужно не просто получить имена, а, например, проверить, является ли строка допустимым значением enum, можно пойти от обратного — работать со значениями. Но для получения именно имен я предлагаю ещё один лаконичный способ с использованием TypeScript-утилит:
enum Fruit {
Apple = 'apple',
Banana = 'banana',
Cherry = 5
}
// Получаем только строковые ключи (имена)
type EnumNames<T> = keyof T & string;
type FruitNames = EnumNames<typeof Fruit>; // "Apple" | "Banana" | "Cherry"
// Функция для безопасного получения имен
function getEnumKeys<T extends Record<string, string | number>>(enumObj: T): (keyof T)[] {
return Object.keys(enumObj).filter(
(key) => !enumObj[key as keyof T] || typeof enumObj[key as keyof T] === 'string' || typeof enumObj[key as keyof T] === 'number'
) as (keyof T)[];
}
const fruitNames = getEnumKeys(Fruit);
console.log(fruitNames); // ["Apple", "Banana", "Cherry"]
Когда это полезно
- Если вы пишете библиотеку или утилиту, которая должна работать с любыми enum.
- Если хотите сохранить типизацию — возвращаемый массив будет содержать именно имена ключей, а не просто строки.
- Для случаев, когда enum может быть пустым или содержать только числовые значения — фильтрация всё равно сработает.
Этот подход чуть сложнее, но даёт больше контроля над типами. Для простых сценариев достаточно первого ответа с isNaN.
Ответов пока нет.
Войдите чтобы оставить ответ