¿Cómo convierto una cadena a enumeración en TypeScript?

312

He definido la siguiente enumeración en TypeScript:

enum Color{
    Red, Green
}

Ahora en mi función recibo el color como una cadena. He intentado el siguiente código:

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

¿Cómo puedo convertir ese valor en una enumeración?

Amitabh
fuente

Respuestas:

431

Las enumeraciones en TypeScript 0.9 se basan en cadenas + números. No debería necesitar la aserción de tipo para conversiones simples:

enum Color{
    Red, Green
}

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

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

Pruébalo en línea

Tengo documentación sobre este y otros patrones de Enum en mi libro OSS: https://basarat.gitbook.io/typescript/type-system/enums

basarat
fuente
112
Esto no funciona con --noImplicitAny(en VS desmarcado "Permitir tipos 'cualquiera' implícitos"). Produce error TS7017: Index signature of object type implicitly has an 'any' type.Para mí esto funcionó: var color: Color = (<any>Color)[green];(probado con la versión 1.4)
Vojta
3
@Vojta dijo que sí. No funciona en VS 2012. Este funcionó pero var color: Color = (<any> Color) [verde];
Faisal Mq
3
Tampoco funciona aquí, la documentación oficial parece confirmar que: typescriptlang.org/docs/handbook/release-notes/…
Pieter De Bie
26
Asegúrese de usar esto si --noImplicitAny var color : Color = Color[green as keyof typeof Color];
Jonas
2
@ Naxos84 Vea mi answear stackoverflow.com/a/56076148/294242
Jonas
123

A partir de Typecript 2.1, las claves de cadena en las enumeraciones están fuertemente tipadas. keyof typeofse utiliza para obtener información sobre las claves de cadena disponibles ( 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

Víctor
fuente
44
Así que podemos usar encasillado:let s = "Green"; let typedColor = <keyof typeof Color> s;
Sergeyt
Sí, y reemplazar letcon constfuncionará sin lanzar. Ejemplo actualizado para aclarar esto. Gracias @SergeyT
Victor
1
typedColorString = Color["Black"];ahora regresaerror TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'
Dominik
2
Una respuesta de una línea:const color: Color = Color[colorString as keyof typeof Color];
cscan
38

Si está seguro de que una cadena de entrada tiene una coincidencia exacta con Color enum, use:

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

En el caso de que una cadena de entrada no coincida con Enum, use:

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

Patio de recreo


Si no enviamos enuma <any>escribir, TypeScript mostrará el error:

El elemento tiene implícitamente 'cualquier' tipo porque la expresión de índice no es del tipo 'número'.

Significa que, de manera predeterminada, el tipo de enumeración TypeScript funciona con índices numéricos, es decir let c = Color[0], pero no con índices de cadena como let c = Color["string"]. Esta es una restricción conocida por el equipo de Microsoft para los índices de cadenas de objetos de problemas más generales .

Artru
fuente
También puede convertir a <clave de tipo de color>. Además, "0" también es una entrada incorrecta, pero no volverá indefinida, por lo tanto, compruebe typeof mayBeColor === 'number'
Quentin 2
@ Quentin2 ¿qué pasa con una cadena numérica? es decir, typeof '0'debería serstring
Patrick Michaelsen
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

Este ejemplo funciona con --noImplicitAnyTypeScript

Fuentes:
https://github.com/Microsoft/TypeScript/issues/13775#issuecomment-276381229 https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types

Jonas
fuente
No sé por qué, pero esta solución no funciona en una enumeración constante (usando el mecanografiado 3.8.3)
Robin-Hoodie
30

Esta nota se relaciona con la respuesta de basarat , no con la pregunta original.

Tuve un problema extraño en mi propio proyecto en el que el compilador estaba dando un error aproximadamente equivalente a "no se puede convertir la cadena a Color" usando el equivalente de este código:

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

Descubrí que la inferencia de tipo de compilador se estaba confundiendo y pensé que colorIdera un valor de enumeración y no una identificación. Para solucionar el problema, tuve que lanzar la ID como una cadena:

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

No estoy seguro de qué causó el problema, pero dejaré esta nota aquí en caso de que alguien tenga el mismo problema que yo.

Sly_cardinal
fuente
¡Gracias! Este es un problema bastante tonto y difícil de entender cuál es el problema. Tal vez Typecript debería considerar encontrar una mejor manera de manejar las enumeraciones.
ChickenFeet
25

Lo tengo funcionando usando el siguiente código.

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

Si proporciona valores de cadena a su enumeración, un reparto directo funciona bien.

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

const color = "Green";
const colorEnum = color as Color;
alexania
fuente
1
Muy simple. ¡Agradable!
Bernoulli IT
1
Esto podría ser engañoso ya que no protege contra colores no válidos. const colorEnum = "Blue" as Colorno se equivocará, y se quedaría pensando que colorEnumestá bien. Pero si lo console.loghicieras, verías "Azul". La respuesta de Artru es buena, porque colorEnumlo será undefined, y luego puedes verificar eso específicamente.
M Falanga
20

Dado que usa el mecanografiado: muchas de las soluciones anteriores pueden no funcionar o son demasiado complejas.

Situación : las cadenas no son las mismas que los valores de enumeración (la carcasa es diferente)

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

Solo usa:

const color = "green" as Color
Nick N.
fuente
15

También me encontré con el mismo error del compilador. Solo una variación ligeramente más corta del enfoque de Sly_cardinal.

var color: Color = Color[<string>colorId];
Chris
fuente
Como una adición: en caso de que tenga una enumeración de mecanografiado rellenada por una capa de JavaScript que serializó la enumeración como una cadena (por ejemplo, Asp Web API a través de AngularJS) puede hacer myProp.color = Color[<string><any>myProp.color] Cheers
Victor
1
Esta debe ser la respuesta reconocida.
Miroslav Popov
9

Si el compilador de TypeScript sabe que el tipo de variable es una cadena, esto funciona:

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

De lo contrario, debe convertirlo explícitamente en una cadena (para evitar advertencias del compilador):

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

En tiempo de ejecución ambas soluciones funcionarán.

Luka
fuente
3
¿Por qué no simplemente usar typecast en <string>colorNamelugar de "" + colorName?
SergeyT
7

Hay mucha información mixta en esta pregunta, así que cubramos toda la implementación de TypeScript 2.x + en la Guía de Nick para usar enumeraciones en modelos con TypeScript .

Esta guía es para: personas que crean código del lado del cliente que ingiere un conjunto de cadenas conocidas del servidor que se modelaría convenientemente como una enumeración en el lado del cliente.

Define la enumeración

Comencemos con la enumeración. Debería verse más o menos así:

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

Dos cosas a destacar aquí:

  1. Los declaramos explícitamente como casos de enumeración respaldados por cadenas que nos permiten instanciarlos con cadenas, no con otros números no relacionados.

  2. Hemos añadido una opción que puede o no existir en nuestro modelo de servidor: UNKNOWN. Esto puede manejarse como undefinedlo prefiere, pero me gusta evitar los | undefinedtipos siempre que sea posible para simplificar el manejo.

Lo bueno de tener un UNKNOWN caso es que puede ser muy obvio al respecto en el código y hacer que los estilos para casos de enumeración desconocidos sean de color rojo brillante y parpadeante para que sepa que no está manejando algo correctamente.

Analiza la enumeración

Es posible que esté utilizando esta enumeración incrustada en otro modelo, o solo, pero tendrá que analizar la enumeración escrita en cadena y desde JSON o XML (ha) en su contraparte fuertemente tipada. Cuando está incrustado en otro modelo, este analizador vive en el constructor de la clase.

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

  return type;
}

Si la enumeración se analiza correctamente, terminará siendo del tipo adecuado. De lo contrario, lo será undefinedy puede interceptarlo y devolver su UNKNOWNcaso. Si prefieres usarundefined como su caso desconocido, puede devolver cualquier resultado del intento de análisis de enumeración.

A partir de ahí, solo es cuestión de usar la función de análisis y usar su nueva variable de tipo fuerte.

const strongIssueType: IssueType = parseIssueType('ACTION');
// IssueType.ACTION
const wrongIssueType: IssueType = parseIssueType('UNEXPECTED');
// IssueType.UNKNOWN
Mella
fuente
66
Desafortunadamente, esto parece no ser correcto o, al menos, no generalizable. Funciona porque sus claves son iguales a las cadenas que se les asignaron. Si ellos, como en mi caso, difieren, sin embargo, esto no funciona. En palabras de la documentación : "Tenga en cuenta que los miembros de enum de cadena no obtienen una asignación inversa generada en absoluto". Su código se compilará para algo así IssueType["REPS"]="REPS". Si hubiera definido su enumeración un poco diferente, digamos, REPS="reps"esto produciría IssueType["REPS"]="reps"lo que ...
altocumulus
... siempre regresa IssueType.UNKNOWNporque no hay clave repsen su enumeración. Lástima, todavía no encontré una solución funcional para esto, ya que mis cadenas contienen guiones que los hacen inutilizables como claves.
altocumulus
Finalmente, encontré una solución en esta respuesta convenciendo al compilador de que esto no era una enumeración de cadenas. Puede valer la pena editar esta información en su propia respuesta.
altocumulus
7

Estaba buscando una respuesta que pueda obtener un enumde a string, pero en mi caso, los valores de las enumeraciones tenían valores de cadena diferentes. El OP tenía una enumeración simple Color, pero tenía algo diferente:

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

Cuando intenta resolver Gender.CantTellcon una "Can't tell"cadena, vuelve undefinedcon la respuesta original.

Otra respuesta

Básicamente, se me ocurrió otra respuesta, fuertemente inspirada por esta respuesta :

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

Notas

  • Tomamos el primer resultado de filter, asumiendo que el cliente está pasando una cadena válida de la enumeración. Si no es el caso, undefinedserá devuelto.
  • Echamos enumObja any, ya que con mecanografiado 3.0+ (en la actualidad el uso de mecanografiado 3.5), el enumObjse resuelve como unknown.

Ejemplo de uso

const cantTellStr = "Can't tell";

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

Nota: Y, como alguien señaló en un comentario, también quería usar el noImplicitAny.

Versión actualizada

Sin yeso anyy tipificaciones adecuadas.

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

Además, la versión actualizada tiene una forma más fácil de llamarlo y es más legible:

stringToEnumValue(Gender, "Can't tell");
elbaid
fuente
6

Necesitaba saber cómo recorrer valores de enumeración (estaba probando muchas permutaciones de varias enumeraciones) y descubrí que esto funcionaba bien:

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
}

Fuente: https://blog.mikeski.net/development/javascript/typescript-enums-to-from-string/

mikeb
fuente
¡Esta respuesta es genial! Quiéralo. Especialmente la forma en que haces una enumeración de la cadena. Esto puede ahorrarle mucho tipeo al probar enumeraciones u otros casos.
Florian Leitgeb
Sí, uso esto con Jest's eachpara probar cada caso de enumeración con un solo método
mikeb
3

Enum

enum MyEnum {
    First,
    Second,
    Three
}

Uso de la muestra

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

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

Ignorar el análisis de mayúsculas y minúsculas

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;
    }
}
Очир Дармаев
fuente
Cualquiera que tenga enumeración como yo debería poner return enumType[property];un estuche cuando su elemento enumeración se parezcaSkills = "anyvalue"
neustart47
@ neustart47, ¿podría hacer la pregunta?
Очир Дармаев
No es una pregunta. Acabo de mencionar algunos cambios para cualquiera que esté buscando el mismo caso que yo. Tu respuesta es correcta.
neustart47
2

Las enumeraciones creadas de la manera que lo hizo se compilan en un objeto que almacena asignaciones directas (name -> value)e inversas (value -> name). Como podemos observar en esta captura de pantalla de Chrome Devtools:

ingrese la descripción de la imagen aquí

Aquí hay un ejemplo de cómo funciona el mapeo dual y cómo transmitir de uno a otro:

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
Willem van der Veen
fuente
1

Prueba esto

var color: Color = (Color como cualquiera) ["Verde];

Eso funciona bien para la versión 3.5.3

Oyeme
fuente
0

Si está utilizando espacios de nombres para ampliar la funcionalidad de su enumeración, también puede hacer algo como

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

y úsalo así

  Color.getInstance('Red');
andrei.b
fuente
0

otra variación puede ser

const green= "Green";

const color : Color = Color[green] as Color;
Anuranjan Srivastav
fuente