Как преобразовать строку в 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 достаточно крут, чтобы сгенерировать объект, содержащий как ключ сопоставления -> значение, так и значение -&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',
}
Здесь следует отметить две вещи:
Мы явно объявляем их как строковые перечисления, которые позволяют нам создавать их экземпляры со строками, а не с некоторыми другими не связанными числами.
Мы добавили параметр, который может существовать или не существовать в нашей модели сервера:
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');