Cum convertez un șir de caractere într-un enum în TypeScript?


Am definit următorul enum în TypeScript:

enum Color{
    Red, Green
}

Acum, în funcția mea primesc culoarea ca un șir de caractere. Am încercat următorul cod:

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

Cum pot să convertez acea valoare într-un enum?

Răspuns #1

Enumurile în TypeScript 0.9 sunt bazate pe șiruri de caractere și numere. Nu ar trebui să fie necesară afirmația de tip pentru conversii simple:

enum Color{
    Red, Green
}

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

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

Încearcă-l online

Am documentație despre acest lucru și alte modele Enum în cartea mea OSS: https://basarat.gitbook.io/typescript/type-system/enums

Asigurați-vă că utilizați acest lucru dacă folosiți flag-ul TypeScript --noImplicitAny:

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

Răspunsul #2

Începând cu TypeScript 2.1, cheile de tip string din enumuri sunt puternic tipizate. keyof typeof este folosit pentru a obține informații despre cheile de tip string disponibile (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

Răspunsul #3

Dacă furnizați valori de tip șir pentru enumul vostru, o conversie directă funcționează perfect.

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

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

Răspunsul #4

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

Acest exemplu funcționează cu --noImplicitAny în TypeScript

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

Răspunsul #5

Având în vedere că folosești TypeScript: Multe dintre soluțiile de mai sus s-ar putea să nu funcționeze sau sunt excesiv de complexe.

Situație: Șirurile nu sunt aceleași cu valorile enum (diferență de majuscule/minuscule)

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

Folosește doar:

const color = "green" as Color

Vă rugăm să rețineți că aceasta nu garantează un enum valid.

Răspunsul #6

Typescript 1.x

Dacă sunteți sigur că un șir de intrare are o potrivire exactă cu enumerația Color atunci folosiți:

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

În cazul în care un șir de intrare poate să nu corespundă Enum, folosiți:

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

Teren de joacă


Dacă nu facem conversia enum la tipul <any>, atunci TypeScript va afișa eroarea:

Elementul are implicit tipul 'any' deoarece expresia indexului nu este de tip 'number'.

Aceasta înseamnă că, în mod implicit, tipul Enum din TypeScript funcționează cu indici numerici, adică let c=Color[0], dar nu cu indici de tip string, ca let c=Color["string"]. Aceasta este o restricție cunoscută de echipa Microsoft pentru problema mai generală Indici de tip string pentru obiecte.

Typescript 2.x-4.x

TypeScript a trecut la conceptul keyof typeof.

Dacă cineva folosește enumerări cu valori de tip șir:

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

apoi există soluția lingvistică de a mapa cheile la valori (Color.Green -> "GRN") doar accesând un membru al unui enum, dar nu există o modalitate simplă de a face inversul ("GRN" -> Color.Green). Din maparea inversă:

Țineți cont că membrii enumerației de tip șir nu primesc deloc o mapare inversă generată.

O posibilă soluție este să verificați manual valorile și să transformați valoarea în enum. Vă rugăm să rețineți că va funcționa doar cu enum-uri de tip șir de caractere.

function enumFromStringValue<T> (enm: { [s: string]: T}, value: string): T | undefined {
  return (Object.values(enm) as unknown as string[]).includes(value)
    ? value as unknown as T
    : undefined;
}

enumFromStringValue(Color, "RD"); // Color.Red
enumFromStringValue(Color, "UNKNOWN"); // undefined
enumFromStringValue(Color, "Red"); // undefined

Răspunsul #7

Cea mai simplă abordare

enum Color { Red, Green }

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

Acest lucru funcționează atât pentru enumerările numerice, cât și pentru cele de tip șir. Nu este nevoie să folosești o aserțiune de tip.

Șiruri de caractere enum necunoscute

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)

Convertiți valorile enum de tip șir

Enumerațiile de tip string nu au o mapare inversă (spre deosebire de cele numerice). Putem crea un ajutor de căutare pentru a converti un șir de caractere al unei valori enum într-un tip enum:

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)

Exemplar de teren de joacă

Răspunsul #8

Am reușit să-l fac să funcționeze folosind următorul cod.

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

Răspunsul #9

Această notă se referă la răspunsul lui basarat, nu la întrebarea originală.

Am avut o problemă ciudată în propriul meu proiect, unde compilatorul dădea o eroare aproximativ echivalentă cu „nu se poate converti șirul în Color” folosind echivalentul acestui cod:

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

Am constatat că inferența tipului făcută de compilator se încurca și credea că colorId era o valoare enum și nu un ID. Pentru a rezolva problema, a trebuit să fac conversia ID-ului la un șir de caractere:

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

Nu sunt sigur ce a cauzat problema, dar voi lăsa această notă aici în caz că cineva se confruntă cu aceeași problemă pe care am avut-o eu.

Răspunsul #10

Dacă compilatorul TypeScript știe că tipul variabilei este string, atunci acest lucru funcționează:

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

Altfel, ar trebui să îl convertești explicit într-un șir de caractere (pentru a evita avertismentele compilatorului):

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

La rulare, ambele soluții vor funcționa.

Răspuns #11

Am întâlnit și eu aceeași eroare de compilator. Doar o variantă puțin mai scurtă a abordării lui Sly_cardinal.

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

Răspunsul #12

Căutam un răspuns care să poată obține un enum dintr-un string, dar în cazul meu, valorile enum-urilor aveau corespondente de șiruri de caractere diferite. OP avea un enum simplu pentru Color, dar eu aveam ceva diferit:

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

Când încerci să rezolvi Gender.CantTell cu un șir de caractere "Can't tell", returnează undefined cu răspunsul original.

Încă un răspuns

Practic, am venit cu un alt răspuns, puternic inspirat de acest răspuns:

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

Note

  • Luăm primul rezultat al filter, presupunând că clientul transmite un șir valid din enum. Dacă nu este cazul, va fi returnat undefined.
  • Facem conversia enumObj la any, deoarece cu TypeScript 3.0+ (în prezent folosind TypeScript 3.5), enumObj este rezolvat ca unknown.

Exemplu de utilizare

const cantTellStr = "Can't tell";

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

Notă: Și, așa cum cineva a subliniat într-un comentariu, am vrut de asemenea să folosesc noImplicitAny.

Versiune actualizată

Fără conversie la any și tipizări corespunzătoare.

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

De asemenea, versiunea actualizată are o modalitate mai ușoară de a o apela și este mai ușor de citit:

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

Răspunsul #13

Aveam nevoie să știu cum să parcurg valorile unui enum (testam multe permutări ale mai multor enumuri) și am descoperit că aceasta funcționează bine:

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
}

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

Răspuns #14

Pe scurt: Fie:

  • Creează o funcție care să analizeze și să convertească valoarea șirului într-un enum.
  • Dacă ai nevoie de numele cheii având valoarea, nu folosi un enum TS.

La început, un enum este o asociere între un nume ușor de citit de către oameni și o valoare, acesta este scopul pentru care a fost creat.

Valori implicite: TS va asigura implicit că aveți o valoare unică pentru cheile definite ale enum-ului.

Acesta

enum Color {
    Red, Green
}

Este echivalent cu

enum Color {
    Red = 0,
    Green = 1
}

Codul js transpilat al ambelor va fi

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

Deoarece acest lucru este de necitit, iată obiectul rezultat odată creat:

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

Acest obiect are proprietăți de tip șir și număr (nu poate exista nicio coliziune deoarece nu poți defini o cheie de enum ca număr). TS este suficient de cool pentru a genera un obiect care conține atât maparea cheie -> valoare cât și valoarea -> cheie.

Mulțumesc lui Dumnezeu că aceasta este o mapare bijectivă, adică fiecare valoare unică are cheia sa unică (și, prin urmare, și inversul este adevărat)

Acum vin problemele, ce se întâmplă dacă forțez utilizarea aceleiași valori?

enum Color {
    Red = 0,
    Green = 0
}

Acesta este obiectul js creat rezultat

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

Nu mai avem bijecția, (asta este surjectiv), nu există nicio mapare magică 0 : ['Green', 'Red']. Doar 0 : 'Green' și am pierdut 0 : 'Red'

Concluzie: TS va încerca întotdeauna să pună harta inversă (valoare -> cheie) atunci când valorile sunt numere.

Acum, după cum probabil știi, poți de asemenea să definești valori de tip șir într-un enum, să schimbăm doar valoarea Green în "Green"

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

Iată obiectul js rezultat

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

După cum puteți vedea, Typescript nu generează mapează valoarea -> cheia. Și nu o va face pentru că ați putea ajunge la o coliziune între o valoare și un nume de cheie. Amintiți-vă: o cheie nu poate fi un număr, prin urmare atunci când valoarea este un număr nu există risc de coliziune.

Acest lucru te face să înțelegi că nu ar trebui să te bazezi pe maparea valoare -> cheie a unui enum. Maparea ar putea pur și simplu să nu existe sau să fie inexactă.

Din nou, un enum este și ar trebui să fie considerat doar ca un nume lizibil de om pentru o valoare. În unele cazuri, TS nici măcar nu va genera o mapare inversă. Acesta este cazul când definești un enum const.

Un enum const este un enum pur la timpul de compilare, TypeScript va înlocui utilizarea enumului cu valoarea sa corespunzătoare la transpilație

De exemplu:

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

Este transpus în

"use strict";

Deci doar ca să spun… nimic pentru că "use strict"; nici măcar nu este legat de ceea ce am scris.

Iată același exemplu cu o utilizare:

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

Este transpus în

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

Așa cum puteți vedea, Color.Green este înlocuit cu „GREEN” de către transpiler.

Deci, revenind la întrebarea originală, cum convertești un șir de caractere într-un enum?

Soluție parser: Îmi pare rău, dar singura metodă curată pe care o recomand este să scrii o funcție; folosirea unui switch case este o modalitate inteligentă de a realiza acest lucru.

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

Soluție enum personalizată:

Rețineți că enumerările TS sunt opace, ceea ce înseamnă că nu există nicio modalitate ca compilatorul să tipărească corect valoarea. Din acest motiv (și mai ales atunci când trebuie să folosiți maparea inversă) aș recomanda să vă faceți propria enumerare astfel:

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

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

Următorul este sigur (color poate lua doar o valoare validă cunoscută). Pe scurt, te bazezi pe uniuni de șiruri în loc de o valoare enum.

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