Как преобразовать строку в enum в TypeScript: полный туториал

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Как преобразовать строку в enum в TypeScript: пошаговое руководство

Вы когда-нибудь сталкивались с ситуацией, когда API возвращает строку, а ваша TypeScript-модель строго типизирована через enum? Или, может быть, вы получаете значение из localStorage и хотите безопасно преобразовать его в перечисление? Если да, то вы попали по адресу. В этой статье мы разберем, как конвертировать строку в enum в TypeScript — от простых решений до продвинутых техник с обработкой ошибок.

Эта тема — одна из самых популярных на Stack Overflow (тысячи просмотров!), и неспроста. Даже опытные разработчики часто путаются, когда дело доходит до безопасного преобразования строк в перечисления. Давайте разложим всё по полочкам.

Что такое enum в TypeScript и зачем его конвертировать?

Enum (перечисление) — это мощный инструмент TypeScript, который позволяет задать набор именованных констант. Вместо того чтобы использовать "магические строки" вроде 'active' или 'pending', вы определяете enum и работаете с типизированными значениями. Но вот загвоздка: данные из внешних источников (API, формы, файлы конфигурации) часто приходят в виде обычных строк. И тут встаёт вопрос: как преобразовать строку в enum TypeScript безопасно и элегантно?

// Пример enum
enum Status {
  Active = 'active',
  Inactive = 'inactive',
  Pending = 'pending'
}

// Строка из API
const apiStatus: string = 'active';
// Как получить Status.Active? 🤔

Способ 1: Прямое приведение типов (Type Assertion)

Самый простой, но и самый опасный метод. Вы просто говорите TypeScript: "Поверь мне, это правильное значение".

const status = apiStatus as Status;
// или
const status = <Status>apiStatus;

Плюс: Минимум кода. Минус: Если строка не соответствует ни одному значению enum, вы получите undefined без предупреждения. TypeScript не выбросит ошибку, и вы узнаете о проблеме только в runtime, когда что-то сломается.

Этот метод стоит использовать только если вы абсолютно уверены в источнике данных. Для продакшена — не рекомендуется.

Способ 2: Использование кастомной функции-утилиты

Это золотая середина между простотой и безопасностью. Напишем функцию, которая принимает строку и проверяет, существует ли такое значение в enum.

function stringToEnum<T>(value: string, enumObject: T): T[keyof T] | undefined {
  // Проверяем, есть ли значение в enum
  const enumValues = Object.values(enumObject) as string[];
  if (enumValues.includes(value)) {
    return value as unknown as T[keyof T];
  }
  return undefined;
}

// Использование
const status = stringToEnum(apiStatus, Status);
if (status) {
  console.log(status); // Status.Active
} else {
  console.error('Некорректное значение статуса');
}

Эта функция безопасно преобразует строку в enum TypeScript. Если значение не найдено, она возвращает undefined, и вы можете обработать этот случай. Обратите внимание на дженерик <T> — он делает функцию универсальной для любого enum.

Способ 3: Строгий подход с выбрасыванием ошибки

Иногда undefined — это не то, что вам нужно. Лучше явно выбросить ошибку, чтобы быстро обнаружить проблему.

function stringToEnumStrict<T>(value: string, enumObject: T): T[keyof T] {
  const enumValues = Object.values(enumObject) as string[];
  if (enumValues.includes(value)) {
    return value as unknown as T[keyof T];
  }
  throw new Error(`Значение "${value}" не найдено в enum`);
}

// Использование
try {
  const status = stringToEnumStrict(apiStatus, Status);
  // Работаем с status
} catch (error) {
  console.error(error.message);
  // Логируем ошибку в Sentry или другую систему мониторинга
}

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

Способ 4: Enum с числовыми значениями

До сих пор мы рассматривали строковые enum. Но TypeScript поддерживает и числовые перечисления. С ними есть нюанс: Object.values() возвращает и ключи, и значения.

enum NumericStatus {
  Active = 1,
  Inactive = 2,
  Pending = 3
}

// Проблема: Object.values(NumericStatus) вернёт [1, 2, 3, 'Active', 'Inactive', 'Pending']
// Это не то, что нам нужно!

Для числовых enum нужно использовать Object.keys() или фильтровать значения:

function numericStringToEnum(value: string, enumObject: any): number | undefined {
  // Проверяем только ключи (строковые имена)
  const keys = Object.keys(enumObject).filter(key => isNaN(Number(key)));
  if (keys.includes(value)) {
    return enumObject[value];
  }
  return undefined;
}

// Или более универсальный вариант
function stringToEnumUniversal<T>(value: string, enumObject: T): T[keyof T] | undefined {
  const keys = Object.keys(enumObject).filter(key => isNaN(Number(key)));
  const values = keys.map(key => enumObject[key as keyof T]);
  
  if (values.includes(value as unknown as T[keyof T])) {
    return value as unknown as T[keyof T];
  }
  
  // Проверяем, может быть value — это имя ключа?
  if (keys.includes(value)) {
    return enumObject[value as keyof T];
  }
  
  return undefined;
}

Этот универсальный метод работает и со строковыми, и с числовыми enum. Но для простоты я рекомендую использовать первый вариант с Object.values(), если вы точно знаете, что работаете со строковым enum.

Способ 5: Декораторы и продвинутые техники

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

function EnumConverter(enumObject: any) {
  return function(target: any, propertyKey: string) {
    let value: any;
    
    Object.defineProperty(target, propertyKey, {
      get: () => value,
      set: (newValue: string) => {
        const enumValue = Object.values(enumObject).includes(newValue) 
          ? newValue 
          : undefined;
        value = enumValue;
      },
      enumerable: true,
      configurable: true
    });
  };
}

class User {
  @EnumConverter(Status)
  status!: Status;
}

const user = new User();
user.status = 'active'; // Автоматически преобразуется в Status.Active
console.log(user.status); // 'active' (строка, но тип — Status)

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

Частые ошибки и как их избежать

Давайте разберём типичные грабли, на которые наступают разработчики при работе с enum и строками:

  • Ошибка 1: Забыть, что enum — это объект. В

Блоги

Рекомендаций по книгам