Как преобразовать строку в enum в TypeScript?
Правильное и безопасное преобразование строки в enum
Проблема, с которой вы столкнулись, очень распространена. Enum в TypeScript — это не просто набор констант, а объект, который имеет двустороннее отображение (для числовых enum) или одностороннее (для строковых). Рассмотрим несколько надёжных подходов.
1. Базовая проверка с помощью includes
Самый простой способ — проверить, является ли строка допустимым значением enum, используя Object.values():
enum OrderStatus {
Pending = "pending",
Processing = "processing",
Shipped = "shipped",
Delivered = "delivered",
Cancelled = "cancelled"
}
function parseOrderStatus(value: string): OrderStatus {
if (Object.values(OrderStatus).includes(value as OrderStatus)) {
return value as OrderStatus;
}
throw new Error(`Invalid OrderStatus value: "${value}"`);
}
// Использование
const status = parseOrderStatus("processing"); // ✅ OrderStatus.Processing
const invalid = parseOrderStatus("unknown"); // ❌ Выбросит ошибку
Важно: Здесь value as OrderStatus используется только для удовлетворения TypeScript, реальная проверка происходит через includes.
2. Функция-утилита с дженериком
Для переиспользования создадим универсальную функцию:
function stringToEnum<T extends Record<string, string>>(
enumObj: T,
value: string
): T[keyof T] {
const values = Object.values(enumObj) as string[];
if (values.includes(value)) {
return value as T[keyof T];
}
throw new Error(`Value "${value}" is not valid for enum`);
}
// Пример использования
const parsed = stringToEnum(OrderStatus, "shipped"); // ✅ OrderStatus.Shipped
3. Обработка undefined и значения по умолчанию
Если вы не хотите выбрасывать ошибку, можно возвращать значение по умолчанию:
function safeParseOrderStatus(value: string): OrderStatus | null {
if (Object.values(OrderStatus).includes(value as OrderStatus)) {
return value as OrderStatus;
}
return null; // или OrderStatus.Pending как fallback
}
// Или с дженериком:
function stringToEnumSafe<T extends Record<string, string>>(
enumObj: T,
value: string,
defaultValue?: T[keyof T]
): T[keyof T] | undefined {
const values = Object.values(enumObj) as string[];
if (values.includes(value)) {
return value as T[keyof T];
}
return defaultValue;
}
const status = safeParseOrderStatus("unknown") ?? OrderStatus.Pending;
4. Type Guard для проверки
Можно создать пользовательский type guard:
function isOrderStatus(value: string): value is OrderStatus {
return Object.values(OrderStatus).includes(value as OrderStatus);
}
// Использование
const raw: string = getFromAPI();
if (isOrderStatus(raw)) {
// TypeScript знает, что raw — это OrderStatus
console.log(raw); // raw имеет тип OrderStatus
} else {
// Обработка ошибки
}
5. Для числовых enum (дополнительно)
Числовые enum имеют обратное отображение, поэтому нужна дополнительная фильтрация:
enum NumericStatus {
Active = 1,
Inactive = 0,
Pending = 2
}
function numericEnumFromString(enumObj: any, value: number): NumericStatus {
// Отфильтровываем строковые ключи (обратное отображение)
const numericValues = Object.values(enumObj).filter(v => typeof v === 'number');
if (numericValues.includes(value)) {
return value as NumericStatus;
}
throw new Error(`Invalid numeric value: ${value}`);
}
Рекомендации
- Всегда проверяйте входные данные — не полагайтесь на
asбез валидации. - Используйте дженерики для создания переиспользуемых утилит.
- Для больших проектов рассмотрите библиотеки вроде
enum-utilsилиts-enum-util. - В TypeScript 5.0+ можно использовать
const-параметры для ещё более строгой типизации.
Надеюсь, эти примеры помогут вам безопасно работать с enum! Если остались вопросы — спрашивайте.
<<Альтернативный подход: использование Map для lookup
Если вы работаете с большим количеством enum или часто выполняете преобразование, можно создать обратный lookup с помощью Map. Это будет работать быстрее, чем Object.values + includes (O(1) вместо O(n)):
enum OrderStatus {
Pending = "pending",
Processing = "processing",
Shipped = "shipped"
}
// Создаём Map для быстрого поиска
const statusMap = new Map<string, OrderStatus>(
Object.values(OrderStatus).map(value => [value, value])
);
function parseWithMap(value: string): OrderStatus {
const result = statusMap.get(value);
if (!result) {
throw new Error(`Invalid status: "${value}"`);
}
return result;
}
// Использование
console.log(parseWithMap("shipped")); // OrderStatus.Shipped
Почему это может быть полезно?
- Производительность: Map.has/get работают за O(1) в среднем.
- Читаемость: Код явно показывает, что мы ищем значение в заготовленном словаре.
- Безопасность: Map гарантирует, что ключи уникальны и соответствуют значениям enum.
Этот подход особенно удобен, если enum определён в отдельном модуле и вы не хотите каждый раз вызывать Object.values. Вы можете экспортировать Map вместе с enum для использования в других частях приложения.