Jak przekonwertować ciąg znaków na wyliczanie w TypeScript?


311

Zdefiniowałem następujące wyliczenie w TypeScript:

enum Color{
    Red, Green
}

Teraz w mojej funkcji otrzymuję kolor jako ciąg. Wypróbowałem następujący kod:

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

Jak przekonwertować tę wartość na wyliczenie?

Odpowiedzi:


430

Wyliczenia w TypeScript 0.9 są oparte na ciągu + liczbach. Nie powinieneś potrzebować asercji typu dla prostych konwersji:

enum Color{
    Red, Green
}

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

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

Wypróbuj online

Mam dokumentację dotyczącą tego i innych wzorców Enum w mojej książce OSS: https://basarat.gitbook.io/typescript/type-system/enums


111
To nie działa z --noImplicitAny(w VS niezaznaczone „Zezwól domyślnie na dowolne” typy). To produkuje error TS7017: Index signature of object type implicitly has an 'any' type.dla mnie to działało: var color: Color = (<any>Color)[green];(testowane z wersją 1.4)
Vojta

3
@Vojta powiedział dobrze. To nie działa w VS 2012. Ten działał, ale var color: Color = (<any> Color) [green];
Faisal Mq

3
Tu też nie działa, oficjalna dokumentacja wydaje się potwierdzać, że: typescriptlang.org/docs/handbook/release-notes/…
Pieter De Bie

26
Upewnij się, że używasz tego, jeśli --noImplicitAny var color : Color = Color[green as keyof typeof Color];
Jonas

2
@ Naxos84 Zobacz mój answear stackoverflow.com/a/56076148/294242
Jonas

122

Od Typescript 2.1 klucze ciągowe w wyliczeniach są silnie typowane. keyof typeofsłuży do uzyskania informacji o dostępnych kluczach łańcuchowych ( 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
Możemy więc użyć typecasta:let s = "Green"; let typedColor = <keyof typeof Color> s;
SergeyT

Tak, i zastąpienie letgo constbędzie działać bez rzucania. Zaktualizowany przykład, aby to wyjaśnić. Dzięki @SergeyT
Victor

1
typedColorString = Color["Black"];teraz powracaerror TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'
Dominik

2
Odpowiedź w jednym wierszu:const color: Color = Color[colorString as keyof typeof Color];
cscan,

38

Jeśli masz pewność, że łańcuch wejściowy ma dokładne dopasowanie do wyliczenia koloru, użyj:

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

W przypadku, gdy ciąg wejściowy może nie pasować do Enum, użyj:

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

Plac zabaw


Jeśli nie przerzucimy enumdo <any>pisania, TypeScript wyświetli błąd:

Element niejawnie ma „dowolny” typ, ponieważ wyrażenie indeksu nie jest typu „liczba”.

Oznacza to, że domyślnie typ Enum TypeScript działa z indeksami liczbowymi, tj. let c = Color[0]Ale nie z takimi jak indeksy łańcuchowe let c = Color["string"]. Jest to znane ograniczenie zespołu Microsoft dotyczące bardziej ogólnego problemu Indeksy ciągów obiektów .


Możesz także rzutować na <keyof typeof Color>. Również „0” jest niepoprawne, ale nie zwróci niezdefiniowanego, więc sprawdź typeof mayBeColor === 'number'
Quentin 2

@ Quentin2 co z ciągiem liczbowym? tzn. typeof '0'powinno byćstring
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

Ten przykład działa z --noImplicitAnyTypeScript

Źródła:
https://github.com/Microsoft/TypeScript/issues/13775#issuecomment-276381229 https://www.typescriptlang.org/docs/handbook/advanced-types.html#index-types


Nie wiem dlaczego, ale to rozwiązanie nie działa na stałym enum (używając Typescript 3.8.3)
Robin-Hoodie

30

Ta uwaga dotyczy basarat za odpowiedź , a nie oryginalne pytanie.

Miałem dziwny problem w swoim własnym projekcie, w którym kompilator dawał błąd w przybliżeniu odpowiadający „nie można przekonwertować ciągu na kolor” przy użyciu odpowiednika tego kodu:

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

Odkryłem, że wnioskowanie o typie kompilatora jest mylące, i pomyślałem, że colorIdjest to wartość wyliczana, a nie identyfikator. Aby rozwiązać problem, musiałem rzucić identyfikator jako ciąg znaków:

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

Nie jestem pewien, co spowodowało problem, ale zostawię tę notatkę tutaj na wypadek, gdyby ktoś napotkał ten sam problem, co ja.


Dziękuję Ci! Jest to dość głupiutki problem i trudno zrozumieć, na czym polega problem. Może maszynopis powinien rozważyć wymyślenie lepszego sposobu obsługi wyliczeń.
ChickenFeet

25

Mam go działa przy użyciu następującego kodu.

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

23

Jeśli podasz wartości ciągu do swojego wyliczenia, rzut prosty działa dobrze.

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

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

1
Bardzo prosta. Miły!
Bernoulli IT,

1
Może to być mylące, ponieważ nie chroni przed nieprawidłowymi kolorami. const colorEnum = "Blue" as Colornie popełni błędu, i będziesz myśleć, że colorEnumjest OK. Ale gdybyś do console.logtego doszedł, zobaczyłbyś „niebieski”. Odpowiedź Artru jest miła, ponieważ colorEnumbędzie undefined- i możesz to sprawdzić konkretnie.
M Falanga

20

Biorąc pod uwagę, że używasz maszynopisu: wiele z powyższych rozwiązań może nie działać lub jest zbyt skomplikowanych.

Sytuacja : ciągi znaków nie są takie same jak wartości wyliczeniowe (obudowa różni się)

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

Po prostu użyj:

const color = "green" as Color

15

Wystąpił również ten sam błąd kompilatora. Tylko nieznacznie krótsza odmiana podejścia Sly_cardinal.

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

Jako dodatek: w przypadku, gdy masz wyliczenie maszynopisu wypełnione warstwą javascript, która serializuje wyliczenie jako ciąg (powiedz na przykład Asp Web API przez AngularJS), możesz zrobić myProp.color = Color[<string><any>myProp.color] Pozdrowienia
Victor

1
To musi być uznana odpowiedź.
Miroslav Popov

9

Jeśli kompilator TypeScript wie, że typ zmiennej jest ciągiem, działa to:

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

W przeciwnym razie powinieneś jawnie przekonwertować go na ciąg znaków (aby uniknąć ostrzeżeń kompilatora):

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

W czasie wykonywania oba rozwiązania będą działać.


3
dlaczego nie użyć <string>colorNamezamiast tego typecasta "" + colorName?
SergeyT

7

W tym pytaniu jest wiele mieszanych informacji, dlatego omówmy całą implementację dla TypeScript 2.x + w Przewodniku Nicka dotyczącym korzystania z wyliczeń w modelach z TypeScript .

Ten przewodnik jest przeznaczony dla: osób tworzących kod po stronie klienta, który pobiera zestaw znanych ciągów z serwera, który byłby wygodnie modelowany jako Enum po stronie klienta.

Zdefiniuj wyliczenie

Zacznijmy od wyliczenia. Powinno to wyglądać mniej więcej tak:

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

Warto zwrócić uwagę na dwie rzeczy:

  1. Wyraźnie deklarujemy je jako przypadki wyliczenia oparte na łańcuchach znaków, co pozwala nam tworzyć ich wystąpienia za pomocą łańcuchów, a nie innych niepowiązanych liczb.

  2. Dodaliśmy opcję, która może lub nie może istnieć na nasz model serwera: UNKNOWN. Można sobie z tym poradzić tak undefined, jak wolisz, ale | undefinedw miarę możliwości lubię unikać typów, aby uprościć obsługę.

Wspaniałą rzeczą w posiadaniu UNKNOWNsprawy jest to, że możesz być naprawdę oczywisty w kodzie i tworzyć style dla nieznanych przypadków wyliczeniowych jasnoczerwonych i mrugających, abyś wiedział, że nie radzisz sobie z czymś poprawnie.

Analizuj wyliczenie

Być może używasz tego wyliczenia osadzonego w innym modelu lub zupełnie osobno, ale będziesz musiał przeanalizować wyliczenie napisane ciągiem Y z JSON lub XML (ha) w silnie napisanym odpowiedniku. Parser ten osadzony w innym modelu żyje w konstruktorze klas.

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

  return type;
}

Jeśli wyliczenie zostanie poprawnie przeanalizowane, zakończy się poprawnym typem. W przeciwnym razie będzie undefinedi możesz go przechwycić i zwrócić swoją UNKNOWNsprawę. Jeśli wolisz używać undefinedjako swojego nieznanego przypadku, możesz po prostu zwrócić dowolny wynik z parsowanej analizy enum.

Stamtąd jest tylko kwestia użycia funkcji analizy składni i użycia nowej, silnie wpisanej zmiennej.

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

6
Niestety wydaje się, że nie jest to poprawne lub przynajmniej niemożliwe do uogólnienia. Działa, ponieważ twoje klucze są równe przypisanym ciągom znaków. Jeśli jednak, podobnie jak w moim przypadku, różnią się, nie działa to. W słowach dokumentacji : „Należy pamiętać, że członkowie wyliczania ciągów w ogóle nie generują odwrotnego odwzorowania”. Twój kod skompiluje się do czegoś takiego IssueType["REPS"]="REPS". Jeśli zdefiniowałeś swój enum trochę inaczej, powiedzmy, REPS="reps"to dałoby to, IssueType["REPS"]="reps"co ...
altocumulus

... zawsze wracaj, IssueType.UNKNOWNponieważ repsw twoim wyliczeniu nie ma klucza . Szkoda, wciąż nie znalazłem działającego rozwiązania, ponieważ moje ciągi zawierają łączniki, co czyni je bezużytecznymi jako klucze.
altocumulus

Wreszcie znalazłem rozwiązanie w tej odpowiedzi , przekonując kompilator, że nie jest to wyliczenie łańcuchowe. Może warto edytować te informacje we własnej odpowiedzi.
altocumulus,

6

Musiałem wiedzieć, jak ominąć wartości wyliczeniowe (testowałem wiele permutacji kilku wyliczeń) i stwierdziłem, że działa dobrze:

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
}

Źródło: https://blog.mikeski.net/development/javascript/typescript-enums-to-from-string/


Ta odpowiedź jest genialna! Kocham to. Zwłaszcza sposób, w jaki tworzysz wyliczenie z łańcucha. To pozwala zaoszczędzić tyle pisania podczas testowania wyliczeń lub innych przypadków.
Florian Leitgeb,

Tak, używam tego z Jest's eachdo testowania każdego przypadku enum tylko jedną metodą
mikeb

6

Szukałem odpowiedzi, która może uzyskać enumod string, ale w moim przypadku wartości wyliczone miały inny odpowiednik wartości ciągu. OP miał prosty wyliczenie Color, ale miałem coś innego:

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

Gdy próbujesz rozwiązać Gender.CantTellza pomocą "Can't tell"łańcucha, zwracaundefined on oryginalną odpowiedź.

Kolejna odpowiedź

Zasadniczo wpadłem na inną odpowiedź, silnie zainspirowaną tą odpowiedzią :

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

Notatki

  • Bierzemy pierwszy wynik z filter, przy założeniu, że klient przechodzi ważny ciąg z wyliczenia. Jeśli tak nie jest,undefined zostanie zwrócone.
  • Rzucamy enumObjna any, ponieważ w TypeScript 3.0+ (obecnie używa TypeScript 3.5), enumObjjest rozwiązany jako unknown.

Przykład użycia

const cantTellStr = "Can't tell";

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

Uwaga: I, jak ktoś zauważył w komentarzu, chciałem również użyć noImplicitAny .

Zaktualizowana wersja

Brak obsady anyi poprawne pisanie.

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

Zaktualizowana wersja ma również łatwiejszy sposób nazywania i jest bardziej czytelna:

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

3

Enum

enum MyEnum {
    First,
    Second,
    Three
}

Przykładowe użycie

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

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

Zignoruj ​​analizę składni z rozróżnianiem wielkości liter

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

Każdy, kto ma enum, taki jak ja, powinien return enumType[property];Skills = "anyvalue"
wnieść

@ neustart47 czy mógłbyś zadać pytanie?
Очир Дармаев

to nie jest pytanie. Właśnie wspomniałem o zmianach dla każdego, kto szuka tego samego przypadku, co ja. Twoja odpowiedź jest poprawna.
neustart47

2

Wyliczenia utworzone w ten sposób zostały skompilowane w obiekt, który przechowuje oba do przodu (name -> value)(value -> name) mapowanie do i do tyłu . Jak możemy zobaczyć z tego chromowanego zrzutu devtools:

wprowadź opis zdjęcia tutaj

Oto przykład działania podwójnego mapowania i sposobu przesyłania z jednego do drugiego:

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

Spróbuj tego

var color: Color = (Color as any) [„Green];

Działa to dobrze w wersji 3.5.3


0

Jeśli używasz przestrzeni nazw w celu rozszerzenia funkcjonalności Twojego wyliczenia, możesz również zrobić coś takiego

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

i użyj tego w ten sposób

  Color.getInstance('Red');

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.