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


312

Я определил следующее перечисление в TypeScript:

enum Color{
    Red, Green
}

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

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

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

Ответы:


431

Перечисления в 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.gitbook.io/typescript/type-system/enums


112
Это не работает с --noImplicitAny(в VS не отмечено «Разрешить неявные« любые »типы»). Она производит error TS7017: Index signature of object type implicitly has an 'any' type.Для меня это работает: var color: Color = (<any>Color)[green];(протестирована с версией 1.4)
Войт

3
@ Войта сказал правильно. Это не работает в VS 2012. Этот работал, но var color: Color = (<любой> цвет) [зеленый];
Фейсал Mq

3
Здесь это тоже не работает, официальная документация подтверждает, что: typescriptlang.org/docs/handbook/release-notes/…
Питер Де Би

26
Убедитесь, что используете это, если --noImplicitAny var color : Color = Color[green as keyof typeof Color];
Jonas

2
@ Naxos84 Смотрите мой ответ stackoverflow.com/a/56076148/294242
Джонас

123

Начиная с 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#index-types


4
Таким образом, мы можем использовать let s = "Green"; let typedColor = <keyof typeof Color> s;
Typecast

Да, и замена letна constбудет работать без кастинга. Обновленный пример, чтобы прояснить это. Спасибо @SergeyT
Виктор

1
typedColorString = Color["Black"];теперь возвращаетсяerror TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'
Доминик

2
Ответ в одну строку:const color: Color = Color[colorString as keyof typeof Color];
cscan

38

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

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

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

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

Игровая площадка


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

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

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


Вы также можете привести к <keyof typeof Color>. Также «0» также является неправильным вводом, но не будет возвращать неопределенное значение, поэтому проверьте typeof mayBeColor === 'number'
Квентин 2

@ Quentin2 как насчет числовой строки? то есть typeof '0'должно бытьstring
Патрик

36
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#issuecomment-276381229 https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types


Я не знаю почему, но это решение не работает для константного перечисления (используя Typescript 3.8.3)
Робин Гуди

30

Это замечание относится к basarat в ответ , а не оригинальный вопрос.

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

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

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

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

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


Спасибо! Это довольно глупая проблема, и ее трудно понять, в чем заключается проблема. Возможно, Typescript должен рассмотреть вопрос о том, чтобы придумать лучший способ обработки перечислений.
ChickenFeet

25

Я получил это с помощью следующего кода.

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

23

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

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

const color = "Green";
const colorEnum = color as Color;

1
Очень просто. Ницца!
Бернулли ИТ

1
Это может вводить в заблуждение, поскольку не защищает от неправильных цветов. const colorEnum = "Blue" as Colorне ошибется, и вы останетесь думать, что colorEnumвсе в порядке. Но если бы вы были к console.logэтому, вы бы увидели «Синий». Ответ Артру хорош, потому что colorEnumбудет undefined- и вы можете проверить это специально.
М Фаланга

20

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

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

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

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

const color = "green" as Color

15

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

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

В дополнение: в случае, если у вас есть перечисление машинописного текста, заполненное слоем javascript, который сериализует перечисление как строку (например, Asp Web API через AngularJS), вы можете сделать myProp.color = Color[<string><any>myProp.color] Cheers
Victor

1
Это должен быть признанный ответ.
Мирослав Попов

9

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

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

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

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

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


3
почему бы просто не использовать typecast <string>colorNameвместо "" + colorName?
SergeyT

7

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

Это руководство предназначено для: людей, которые создают код на стороне клиента, который принимает набор известных строк с сервера, который будет удобно моделироваться как 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

6
К сожалению, это кажется неправильным или, по крайней мере, не обобщаемым. Это работает, потому что ваши ключи равны строкам, которые они получили. Если они, как в моем случае, отличаются, однако, это не работает. В словах документации : «Имейте в виду, что члены строкового перечисления вообще не генерируют обратное отображение». Ваш код будет скомпилирован в нечто подобное IssueType["REPS"]="REPS". Если бы вы определили ваше перечисление немного по-другому, скажем, REPS="reps"это дало IssueType["REPS"]="reps"бы результат ...
altocumulus

... всегда возвращайся, IssueType.UNKNOWNпотому что repsв твоем перечислении нет ключа . Жаль, что я до сих пор не нашел рабочего решения для этого, так как мои строки содержат дефисы, которые делают их непригодными в качестве ключей.
альто-кучевые

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

7

Я искал ответ, который можно получить enumот a string, но в моем случае значения перечислений имели разные строковые значения. У OP был простой enum для 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");

6

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

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
}

Источник: https://blog.mikeski.net/development/javascript/typescript-enums-to-from-string/


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

Да, я использую это с Jest eachдля проверки каждого перечисления только одним методом
mikeb

3

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;
    }
}

Любой, у кого есть enum, такой как я, должен поместить return enumType[property];случай, когда ваш элемент enum выглядит такSkills = "anyvalue"
neustart47

@ neustart47 не могли бы вы задать вопрос?
Очир Дармаев

это не вопрос. Я только что упомянул некоторые изменения для тех, кто ищет то же дело, что и я. Ваш ответ правильный.
neustart47

2

Перечисления, созданные так, как вы это сделали, скомпилированы в объект, в котором хранятся как прямые, так (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

1

Попробуй это

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

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


0

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

    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');

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.