Как преобразовать строку в enum в TypeScript?

Я определил следующий enum в TypeScript?

enum Color{
    Red, Green
}

Теперь в моей функции я получаю цвет в виде строки. Я пробовал следующий код.

var green= "Green";
var color : Color = <Color>green; // Error: can't convert string to enum

Как я могу преобразовать это значение в enum?

30 ответов

Решение

Перечисления в TypeScript 0.9 основаны на строках + числах. Вам не нужно утверждение типа для простых преобразований:

enum Color{
    Red, Green
}

// To String
 var green: string = Color[Color.Green];

// To Enum / number
var color : Color = Color[green];

Попробуйте онлайн

У меня есть документация об этом и других шаблонах Enum в моей книге OSS: https://basarat.gitbooks.io/typescript/content/docs/enums.html

Начиная с Typescript 2.1 строковые ключи в перечислениях строго типизированы. keyof typeof используется для получения информации о доступных строковых ключах ( 1):

enum Color{
    Red, Green
}

let typedColor: Color = Color.Green;
let typedColorString: keyof typeof Color = "Green";

// Error "Black is not assignable ..." (indexing using Color["Black"] will return undefined runtime)
typedColorString = "Black";

// Error "Type 'string' is not assignable ..." (indexing works runtime)
let letColorString = "Red";
typedColorString = letColorString;

// Works fine
typedColorString = "Red";

// Works fine
const constColorString = "Red";
typedColorString = constColorString

// Works fine (thanks @SergeyT)
let letColorString = "Red";
typedColorString = letColorString as keyof typeof Color;

typedColor = Color[typedColorString];

https://www.typescriptlang.org/docs/handbook/advanced-types.html

Если вы предоставляете строковые значения для своего перечисления, прямое приведение работает нормально.

enum Color {
  Green = "Green",
  Red = "Red"
}

const color = "Green";
const colorEnum = color as Color;
enum Color{
    Red, Green
}

// To String
 var green: string = Color[Color.Green];

// To Enum / number
var color : Color = Color[green as keyof typeof Color]; //Works with --noImplicitAny

Этот пример работает с --noImplicitAny в TypeScript

Источники:
https://github.com/Microsoft/TypeScript/issues/13775 https://www.typescriptlang.org/docs/handbook/advanced-types.html

Если вы используете машинописный текст: многие из вышеперечисленных решений могут не работать или быть слишком сложными.

Ситуация: строки не совпадают со значениями перечисления (регистр отличается)

enum Color {
  Green = "green",
  Red = "red"
}

Просто используйте:

const color = "green" as Color

Если вы уверены, что входная строка точно совпадает с Color enum, используйте:

const color: Color = (<any>Color)["Red"];

В случае, если входная строка может не соответствовать Enum, используйте:

const mayBeColor: Color | undefined = (<any>Color)["WrongInput"];
if(mayBeColor !== undefined){
     //TSC will understand that mayBeColor of type Color here
}

Детская площадка


Если мы не бросим enum в <any> типа тогда tsc покажет ошибку

Элемент неявно имеет тип 'any', потому что индексное выражение не относится к типу 'number'.

Это означает, что по умолчанию тип TS Enum работает с числовыми индексами, т.е.let c = Color[0], но не со строковыми индексами, такими как let c = Color["string"], Это известное ограничение команды Microsoft для более общих проблемных строковых индексов объектов.

Самый простой подход

enum Color { Red, Green }

const c1 = Color["Red"]
const redStr = "Red" // important: use `const`, not mutable `let`
const c2 = Color[redStr]

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

Неизвестные строки перечисления

Простой, небезопасный вариант
const redStrWide: string = "Red" // wide, unspecific typed string
const c3 = Color[redStrWide as keyof typeof Color]
Безопасный вариант с чеками
const isEnumName = <T>(str: string, _enum: T): str is Extract<keyof T, string> =>
    str in _enum
const enumFromName = <T>(name: string, _enum: T) => {
    if (!isEnumName(name, _enum)) throw Error() // here fail fast as an example
    return _enum[name]
}
const c4 = enumFromName(redStrWide, Color)

Преобразование строковых значений перечисления

Перечисления строк не имеют обратного отображения (в отличие от числовых). Мы можем создать помощник поиска для преобразования строки значения перечисления в тип перечисления:

enum ColorStr { Red = "red", Green = "green" }

const c5_by_name = ColorStr["Red"] // ✅ this works
const c5_by_value_error = ColorStr["red"] // ❌ , but this not

const enumFromValue = <T extends Record<string, string>>(val: string, _enum: T) => {
    const enumName = (Object.keys(_enum) as Array<keyof T>).find(k => _enum[k] === val)
    if (!enumName) throw Error() // here fail fast as an example
    return _enum[enumName]
}

const c5 = enumFromValue("red", ColorStr)

Образец детской площадки

Получил работу, используя следующий код.

var green= "Green";
var color : Color= <Color>Color[green];

Эта заметка относится к ответу Басарата, а не к исходному вопросу.

У меня была странная проблема в моем собственном проекте, когда компилятор выдавал ошибку, примерно эквивалентную "не может преобразовать строку в цвет", используя эквивалент этого кода:

var colorId = myOtherObject.colorId; // value "Green";
var color: Color = <Color>Color[colorId]; // TSC error here: Cannot convert string to Color.

Я обнаружил, что вывод типа компилятора запутался, и он подумал, что colorId было значение перечисления, а не ID. Чтобы решить проблему, мне пришлось преобразовать идентификатор в строку:

var colorId = <string>myOtherObject.colorId; // Force string value here
var color: Color = Color[colorId]; // Fixes lookup here.

Я не уверен, что вызвало проблему, но я оставлю эту заметку здесь на случай, если кто-нибудь столкнется с той же проблемой, что и я.

Если компилятор машинописного текста знает, что тип переменной - строка, это работает

let colorName : string = "Green";
let color : Color = Color[colorName];

в противном случае вы должны явно преобразовать его в строку (чтобы избежать предупреждения компилятора)

let colorName : any = "Green";
let color : Color = Color["" + colorName];

Во время выполнения оба решения будут работать.

Я также столкнулся с той же ошибкой компилятора. Просто небольшая вариация подхода Sly_cardinal.

var color: Color = Color[<string>colorId];

Я искал ответ, на который можно enum из string, но в моем случае значения перечислений имели разные аналоги строковых значений. У OP было простое перечисление дляColor, но у меня было что-то другое:

enum Gender {
  Male = 'Male',
  Female = 'Female',
  Other = 'Other',
  CantTell = "Can't tell"
}

Когда вы пытаетесь разрешить Gender.CantTell с "Can't tell" строка, она возвращает undefined с оригинальным ответом.

Другой ответ

По сути, я придумал другой ответ, сильно вдохновленный этим ответом:

export const stringToEnumValue = <ET, T>(enumObj: ET, str: string): T =>
  (enumObj as any)[Object.keys(enumObj).filter(k => (enumObj as any)[k] === str)[0]];

Заметки

  • Берем первый результат изfilter, предполагая, что клиент передает действительную строку из перечисления. Если это не так,undefined будет возвращен.
  • Мы бросаем enumObj к any, потому что с TypeScript 3.0+ (в настоящее время используется TypeScript 3.5) enumObj решается как unknown.

Пример использования

const cantTellStr = "Can't tell";

const cantTellEnumValue = stringToEnumValue<typeof Gender, Gender>(Gender, cantTellStr);
console.log(cantTellEnumValue); // Can't tell

Примечание. И, как кто-то отметил в комментарии, я также хотел использовать noImplicitAny.

Обновленная версия

Нет передачи any и правильный набор текста.

export const stringToEnumValue = <T, K extends keyof T>(enumObj: T, value: string): T[keyof T] | undefined =>
  enumObj[Object.keys(enumObj).filter((k) => enumObj[k as K].toString() === value)[0] as keyof typeof enumObj];

Кроме того, обновленная версия имеет более простой способ называть ее и более читабельна:

stringToEnumValue(Gender, "Can't tell");

Мне нужно было знать, как перебирать значения перечислений (тестировал множество перестановок нескольких перечислений), и я обнаружил, что это хорошо работает:

export enum Environment {
    Prod = "http://asdf.com",
    Stage = "http://asdf1234.com",
    Test = "http://asdfasdf.example.com"
}

Object.keys(Environment).forEach((environmentKeyValue) => {
    const env = Environment[environmentKeyValue as keyof typeof Environment]
    // env is now equivalent to Environment.Prod, Environment.Stage, or Environment.Test
}

TL;DR: Либо:

  • Создайте функцию, которая анализирует и преобразует строковое значение в перечисление.
  • Если вам нужно имя ключа с заданным значением, не используйте перечисление TS.

Во-первых, перечисление — это сопоставление между удобочитаемым именем и значением, для этого оно и создано.

Значения по умолчанию:TS по умолчанию гарантирует, что у вас есть уникальное значение для определенных ключей перечисления.

Этот

      enum Color {
    Red, Green
}

Эквивалентно

      enum Color {
    Red = 0,
    Green = 1
}

Перенесенный js-код обоих будет

      "use strict";
var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Green"] = 1] = "Green";
})(Color || (Color = {}));

Поскольку это нечитаемо, вот созданный объект:

      {0: 'Red', 1: 'Green', Red: 0, Green: 1}

Этот объект имеет строковые и числовые свойства (не может быть никаких коллизий, потому что вы не можете определить ключ перечисления как число). TS достаточно крут, чтобы сгенерировать объект, содержащий как ключ сопоставления -> значение, так и значение -&amp;gt; ключ .

Слава богу, это биективное отображение, т.е. каждое уникальное значение имеет свой уникальный ключ (и, следовательно, верно и обратное).

Теперь возникают проблемы, что, если я заставлю использовать одно и то же значение?

      enum Color {
    Red = 0,
    Green = 0
}

Это результирующий созданный объект js

      {0: 'Green', Red: 0, Green: 0}

У нас больше нет биекции (это surjectif), нет магического отображения 0 : ['Green', 'Red']. Только 0 : 'Green'и мы потеряли 0 : 'Red'

Вывод:TS всегда будет пытаться поставить обратную карту (значение -> ключ), когда значения являются числами.

Теперь, как вы, возможно, знаете, вы также можете определить строковые значения в перечислении, давайте изменим только значение Green на «Green».

      enum Color {
    Red = 0,
    Green = "GREEN"
}

Вот полученный объект js

      {0: 'Red', Red: 0, Green: 'GREEN'}

Как видите, Typescript не генерирует значение сопоставления -> ключ. И этого не произойдет, потому что вы можете столкнуться с конфликтом между значением и именем ключа. Помните: ключ не может быть числом, поэтому, когда значение является числом, нет риска коллизии.

Это заставляет вас понять, что вы не должны полагаться на сопоставление значений -> ключей перечисления. Отображение может просто отсутствовать или быть неточным.

Опять же, перечисление является и должно рассматриваться только как удобочитаемое имя для значения. В некоторых случаях ts вообще не будет генерировать никакого обратного отображения. Это тот случай, когда вы определяете enum const.

Const enum — это чистое перечисление времени компиляции, TS заменит использование перечисления соответствующим значением при транспиляции.

Например : _

      const enum Color {
    Red = 0,
    Green = "GREEN"
}

Транспилируется в

      "use strict";

Так что просто сказать… ничего, потому что «используйте строго»; даже не относится к тому, что мы написали.

Вот тот же пример с использованием:

      const enum Color {
    Red = 0,
    Green = "GREEN"
}
console.log(Color.Green);

Транспилируется в

      "use strict";
console.log("GREEN" /* Green */);

Как видите, Color.Green заменяется транспайлером на "GREEN".

Итак, вернемся к исходному вопросу: как преобразовать строку в перечисление?

Решение синтаксического анализатора: извините, но единственный чистый способ, который я рекомендую, - это написать функцию, использование регистра переключателей - умный способ добиться этого.

      function parseColorName(color: string): Color {
  switch (color) {
    case 'Red': return Color.Red;
    case 'Green': return Color.Green;
    default: throw new Error('unknown color');
  }
}

Пользовательское решение перечисления:

Обратите внимание, что перечисления TS непрозрачны, а это означает, что компилятор не может правильно ввести значение. По этой причине (и особенно когда вам нужно использовать обратное отображение) я бы рекомендовал сделать собственное перечисление следующим образом:

      export const ColorType = {
  RED: 'Red',
  GREEN: 'Green',
} as const;

export type ColorType = typeof ColorType[keyof typeof ColorType];

Следующее безопасно ( colorможет принимать только допустимое известное значение). Короче говоря, вы полагаетесь на объединение строк вместо значения перечисления.

      const color: ColorType= "Green";
// And if you need to create a color from the enum like value:
const anotherColor: ColorType = ColorType.RED;

Почти во всех ответах используются небезопасные приведения (asилиis). Этот не:

      
enum Color {
    Red = "red",
    Green = "green"
}

const colorsMap = new Map<string,Color>(Object.values(Color).map((v) => [v,v]))

function parseColor(volumeData: string): Color | undefined {
    return colorsMap.get(volumeData)
}

const color = parseColor("red")

В этом вопросе много смешанной информации, поэтому давайте рассмотрим всю реализацию Typescript 2.x+ в Руководстве Ника по использованию Enums в моделях с Typescript.

Это руководство предназначено для: тех, кто создает код на стороне клиента, который принимает на сервер набор известных строк, который будет удобно моделироваться как Enum на стороне клиента.

Определить перечисление

Давайте начнем с enum, он должен выглядеть примерно так:

export enum IssueType {
  REPS = 'REPS',
  FETCH = 'FETCH',
  ACTION = 'ACTION',
  UNKNOWN = 'UNKNOWN',
}

Здесь следует отметить две вещи:

  1. Мы явно объявляем их как строковые перечисления, которые позволяют нам создавать их экземпляры со строками, а не с некоторыми другими не связанными числами.

  2. Мы добавили параметр, который может существовать или не существовать в нашей модели сервера: UNKNOWN, Это может быть обработано как undefined если вы предпочитаете, но мне нравится избегать | undefined по типам по возможности упростить обработку.

Отличная вещь о UNKNOWN Дело в том, что вы можете быть действительно очевидны в коде и делать стили для неизвестных перечислений перечисления ярко-красными и мигающими, чтобы вы знали, что вы что-то неправильно обрабатываете.

Разобрать перечисление

Возможно, вы используете это перечисление, встроенное в другую модель, или все в одиночку, но вам придется проанализировать перечисление со строковым типом y из JSON или XML (ха) в свой строго типизированный аналог. Встраиваемый в другую модель, этот синтаксический анализатор живет в конструкторе класса.

parseIssueType(typeString: string): IssueType {
  const type = IssueType[typeString];
  if (type === undefined) {
    return IssueType.UNKNOWN;
  }

  return type;
}

Если перечисление правильно проанализировано, оно в конечном итоге будет иметь правильный тип. В противном случае это будет undefined и вы можете перехватить его и вернуть свой UNKNOWN дело. Если вы предпочитаете использовать undefined как ваш неизвестный случай, вы можете просто вернуть любой результат из попытки разбора enum.

Оттуда, это только вопрос использования функции разбора и использования вашей новой строго типизированной переменной.

const strongIssueType: IssueType = parseIssueType('ACTION');
// IssueType.ACTION
const wrongIssueType: IssueType = parseIssueType('UNEXPECTED');
// IssueType.UNKNOWN

Если вы имеете дело с TypeScript 4.1+ и перечислениями строк и вам нужен простой преобразователь строки в Enum с безопасностью во время компиляции и во время выполнения, хорошо подойдет следующее:

      export const asEnum = <
  T extends { [key: string]: string },
  K extends keyof T & string
>(
  enumObject: T,
  value: `${T[K]}`
): T[K] => {
  if (Object.values(enumObject).includes(value)) {
    return (value as unknown) as T[K];
  } else {
    throw new Error('Value provided was not found in Enum');
  }
};

enum Test {
  hey = 'HEY',
}

const test1 = asEnum(Test, 'HEY');   // no complaints here
const test2 = asEnum(Test, 'HE');    // compile-time error
const test3 = asEnum(Test, 'HE' as any); // run-time error

Для TS 3.9.x

var color : Color = Color[green as unknown as keyof typeof Color];

Машинопись 3.9 реквизит

enum Color{ RED, GREEN }

const color = 'RED' as Color;

легкий горох, лимонный сок!

Для Typescript >= 4 этот код работал:

      enum Color{
    Red, Green
}

// Conversion :
var green= "Green";
var color : Color = green as unknown as Color; 

Enum

enum MyEnum {
    First,
    Second,
    Three
}

Пример использования

const parsed = Parser.parseEnum('FiRsT', MyEnum);
// parsed = MyEnum.First 

const parsedInvalid= Parser.parseEnum('other', MyEnum);
// parsedInvalid = undefined

Игнорировать регистрозависимый анализ

class Parser {
    public static parseEnum<T>(value: string, enumType: T): T[keyof T] | undefined {
        if (!value) {
            return undefined;
        }

        for (const property in enumType) {
            const enumMember = enumType[property];
            if (typeof enumMember === 'string') {
                if (enumMember.toUpperCase() === value.toUpperCase()) {
                    const key = enumMember as string as keyof typeof enumType;
                    return enumType[key];
                }
            }
        }
        return undefined;
    }
}

Перечисления, созданные так, как вы это сделали, скомпилированы в объект, который хранит оба (name -> value) и обратный (value -> name) отображения. Как мы можем наблюдать из этого скриншота Chrome Devtools:

Вот пример того, как работает двойное сопоставление и как приводить от одного к другому:

enum Color{
    Red, Green
}
// To Number
var greenNr: number = Color['Green'];
console.log(greenNr); // logs 1

// To String
var greenString: string = Color[Color['Green']];  // or Color[Color[1]
console.log(greenString); // logs Green

// In your example

// recieve as Color.green instead of the string green
var green: string = Color[Color.Green];  

// obtain the enum number value which corresponds to the Color.green property
var color: Color = (<any>Color)[green];  

console.log(color); // logs 1

Попробуй это

var color: Color = (Цвет как любой)["Зеленый];

Это прекрасно работает для версии 3.5.3

Большинство этих ответов мне кажутся слишком сложными ...

Вы можете просто создать функцию синтаксического анализа для перечисления, которая ожидает один из ключей в качестве аргументов. При добавлении новых цветов никаких других изменений не требуется.

      enum Color { red, green}

// Get the keys 'red' | 'green' (but not 'parse')
type ColorKey = keyof Omit<typeof Color, 'parse'>;

namespace Color {
  export function parse(colorName: ColorKey ) {
    return Color[colorName];
  }
}

// The key 'red' exists as an enum so no warning is given
Color.parse('red');  // == Colors.red

// Without the 'any' cast you would get a compile-time warning
// Because 'foo' is not one of the keys in the enum
Color.parse('foo' as any); // == undefined

// Creates warning:
// "Argument of type '"bar"' is not assignable to parameter of type '"red" | "green"'"
Color.parse('bar');

Это работает для меня в TypeScript 4.4.3 ссылкеTS Playground .

        const stringToEnumValue = <T extends Record<string, string>, K extends keyof T>(
    enumObj: T,
    value: string,
  ): T[keyof T] | undefined =>
    enumObj[
      Object.keys(enumObj).filter(
        (k) => enumObj[k as K].toString() === value,
      )[0] as keyof typeof enumObj
    ];

  enum Color {
    Red = 'red',
    Green = 'green',
  }

  const result1 = stringToEnumValue(Color, 'yellow'); // undefined
  const result2 = stringToEnumValue(Color, 'green'); // Color.Green

  console.log(result1) // undefined = undefined
  console.log(result2) // Color.Green = "green"

Если вас интересует защита типа того, что в противном случае было бы string (вот как я столкнулся с этой проблемой), это может сработать для вас:

enum CurrencyCode {
  cad = "cad",
  eur = "eur",
  gbp = "gbp",
  jpy = "jpy",
  usd = "usd",
}

const createEnumChecker = <T extends string, TEnumValue extends string>(
  enumVariable: { [key in T]: TEnumValue }
) => {
  const enumValues = Object.values(enumVariable);
  return (value: string | number | boolean): value is TEnumValue =>
    enumValues.includes(value);
};

const isCurrencyCode = createEnumChecker(CurrencyCode);

const input: string = 'gbp';

let verifiedCurrencyCode: CurrencyCode | null = null;
// verifiedCurrencyCode = input;
// ^ TypeError: Type 'string' is not assignable to type 'CurrencyCode | null'.

if (isCurrencyCode(input)) {
  verifiedCurrencyCode = input; // No Type Error 
}

Решение взято из этой проблемы github, в которой обсуждаются общие Enums

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

      enum Color {
  Red,
  Green
}

Создайте карту, но оптимизируйте ее создание, чтобы не делать это часто.

      const reverse = new Map(Object.values(Color).map(item => [item.toString(), item]))

Теперь сбросьте эту функцию и просто используйтеreverseMapчтобы получить перечисление цвета.

      const colorText: string = "Red"
const colorEnum: Color | undefined = reverse.get(colorText)

обратите внимание на потенциалundefined, поскольку вы конвертируете текст в перечисление, вы ДОЛЖНЫ проверить его явно. Это решение также позволяет вам указать значение по умолчанию, используя

      const colorWithFallback: Color = reverse.get("Kind of Yellow") || Color.Red

Большинство предоставленных ответов не предлагают широкой поддержки Enums. Предоставленный OP запросил получение Enum только из строкового значения, но Enums также допускает другие значения.

      interface StandardEnum<T> {
    [id: string]: T | string;
    [nu: number]: string;
}

/**
 * Converts the given representation of the value of one enumerated constant to an equivalent enumerated type.
 *
 * @param type - An enumeration type
 * @param value - A value to convert
 */
export const genericValueToEnum = <T, K extends StandardEnum<T>> (
    type: StandardEnum<T>,
    value: K[keyof K]
): T | undefined => {
    const keys = Object.keys(type); // ...but, not really.
    const values = Object.values(type)
        // Filter enum member names because `Object.values()` includes them.
        .filter((value) => !(
            typeof value === 'string' &&
            keys.includes(value) &&
            type[value] !== value
        ));

    return values.includes(value)
        ? value as unknown as T
        : undefined;
}

Это будет работать для всех перечислений, какими бы сложными (или странными) они ни были, пока они не помечены.

      enum OddEnum {
    None = -1,
    No = 0,
    Yes = 1,
    Twenty = '20'
    Other = 'Other',
    MORE = 'More',
};

genericValueToEnum(OddEnum, -1); // => -1 as OddEnum;
genericValueToEnum(OddEnum, 'Other'); // => 'Other' as OddEnum;
genericValueToEnum(OddEnum, 'MORE'); // => undefined;

Другие варианты могут быть

const green= "Green";

const color : Color = Color[green] as Color;

Если вы используете пространства имен для расширения функциональности своего перечисления, вы также можете сделать что-то вроде

    enum Color {
        Red, Green
    }

    export namespace Color {
      export function getInstance(color: string) : Color {
        if(color == 'Red') {
          return Color.Red;
        } else if (color == 'Green') {
          return Color.Green;
        }
      }
    }

и используйте это так

  Color.getInstance('Red');
Другие вопросы по тегам