Как преобразовать строку в enum в TypeScript?
Проблема приведения строки к enum
Действительно, прямое приведение через as OrderStatus — это обман компилятора, а не безопасное преобразование. TypeScript не предоставляет встроенного метода для конвертации строки в enum, но есть несколько надёжных подходов.
Решение 1: Функция-утилита с проверкой
Напишем функцию, которая принимает строку, проверяет её наличие в enum и возвращает корректное значение или выбрасывает ошибку:
enum OrderStatus {
Pending = 'pending',
Processing = 'processing',
Shipped = 'shipped',
Delivered = 'delivered',
Cancelled = 'cancelled'
}
function parseOrderStatus(value: string): OrderStatus {
// Проверяем, есть ли значение в enum
if (Object.values(OrderStatus).includes(value as OrderStatus)) {
return value as OrderStatus;
}
throw new Error(`Invalid OrderStatus value: "${value}"`);
}
// Использование
const statusFromApi: string = 'shipped';
try {
const orderStatus = parseOrderStatus(statusFromApi); // ✅ OrderStatus.Shipped
console.log(orderStatus); // 'shipped'
} catch (error) {
console.error(error.message);
}
Решение 2: Type guard + генерализация
Можно сделать более универсальную функцию, которая работает с любым строковым enum:
function stringToEnum<T extends Record<string, string>>(
enumObj: T,
value: string
): T[keyof T] {
const enumValues = Object.values(enumObj) as string[];
if (enumValues.includes(value)) {
return value as T[keyof T];
}
throw new Error(`Value "${value}" is not in enum`);
}
// Использование
const status = stringToEnum(OrderStatus, 'processing'); // ✅ OrderStatus.Processing
const invalid = stringToEnum(OrderStatus, 'unknown'); // ❌ Error
Решение 3: Использование Map для обратного поиска
Если enum содержит много значений, можно создать Map для быстрого доступа:
class OrderStatusHelper {
private static readonly statusMap: Map<string, OrderStatus> = new Map(
Object.entries(OrderStatus)
);
static fromString(value: string): OrderStatus {
const status = this.statusMap.get(value);
if (!status) {
throw new Error(`Unknown status: "${value}"`);
}
return status;
}
}
const status = OrderStatusHelper.fromString('delivered'); // ✅ OrderStatus.Delivered
Решение 4: Безопасное приведение с возвратом значения по умолчанию
Если вы не хотите выбрасывать ошибку, можно вернуть значение по умолчанию:
function safeParseOrderStatus(value: string): OrderStatus | null {
if (Object.values(OrderStatus).includes(value as OrderStatus)) {
return value as OrderStatus;
}
return null; // или значение по умолчанию
}
const status = safeParseOrderStatus('shipped') ?? OrderStatus.Pending;
Важные замечания
- Числовые enum: для числовых enum (
enum Status { Active = 1, Inactive = 2 }) нужно использоватьObject.keys()вместоObject.values(), так как TypeScript создаёт двунаправленное отображение. - Const enum:
const enumне существует в рантайме, поэтому все эти методы не сработают. Для const enum нужно использовать обычные объекты или union-типы. - Типобезопасность: Всегда проверяйте входные данные — это защитит от ошибок выполнения.
Лично я рекомендую второй вариант (универсальная функция) — он переиспользуем и хорошо читается. Для production-кода добавьте логирование ошибок и обработку в сервисном слое.
<<Альтернативный подход: Использование union-типов вместо enum
Многие разработчики в современном TypeScript отказываются от enum в пользу union-типов (строковых литералов). Это решает проблему конвертации на корню:
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
const orderStatuses = {
Pending: 'pending' as const,
Processing: 'processing' as const,
Shipped: 'shipped' as const,
Delivered: 'delivered' as const,
Cancelled: 'cancelled' as const
};
// Теперь любая строка, прошедшая проверку, автоматически является OrderStatus
function parseOrderStatus(value: string): OrderStatus {
const validStatuses: OrderStatus[] = ['pending', 'processing', 'shipped', 'delivered', 'cancelled'];
if (validStatuses.includes(value as OrderStatus)) {
return value as OrderStatus;
}
throw new Error(`Invalid status: "${value}"`);
}
// Использование
const status: OrderStatus = 'shipped'; // ✅ Без конвертации
Почему это может быть лучше?
- Нет двойной структуры: не нужно хранить enum и маппинг отдельно.
- Лучшая интеграция с JSON: строки из API уже являются корректным типом.
- Tree-shaking: union-типы не генерируют JavaScript-код, в отличие от enum.
- Простота: меньше boilerplate-кода для конвертации.
Этот подход особенно удобен, если вы работаете с REST API или GraphQL, где данные приходят в виде строк. Если вам нужны методы и свойства, ассоциированные со статусами, используйте объект-константу с функциями вместо enum.