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?

Amitabh
źródło

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

basarat
źródło
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

Zwycięzca
źródło
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 .

Artru
źródło
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

Jonas
źródło
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.

Sly_cardinal
źródło
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];
Amitabh
źródło
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;
Aleksania
źródło
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
Nick N.
źródło
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];
Chris
źródło
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ć.

Luka
źródło
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
Nacięcie
źródło
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/

mikeb
źródło
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");
elbaid
źródło
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;
    }
}
Очир Дармаев
źródło
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
Willem van der Veen
źródło
1

Spróbuj tego

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

Działa to dobrze w wersji 3.5.3

Oyeme
źródło
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');
andrei.b
źródło
0

inna odmiana może być

const green= "Green";

const color : Color = Color[green] as Color;
Anuranjan Srivastav
źródło