Reputation: 3316
I'd like to get a string literal union from an enum.
For this enum…
enum Weekday {
MONDAY = 'mon',
TUESDAY = 'tue',
WEDNESDAY = 'wed'
}
… I'd like to get this:
type WeekdayType = 'mon' | 'tue' | 'wed';
I tried typeof keyof Weekday
but that resulted in 'MONDAY' | 'TUESDAY' | 'WEDNESDAY'
. Feel like the solution might have to do with mapped types but I can't seem to wrap my head around it.
How do I do this?
Upvotes: 169
Views: 70376
Reputation: 4372
TypeScript 4.1+:
As mentioned, this can be achieved by using Template Literal Types like so:
type WeekdayType = `${Weekday}`;
TypeScript 3.4+:
Following up on @jcalz answer and the comment from @just-boris, here's an example with const assertions:
const Weekday = {
MONDAY: "mon",
TUESDAY: "tue",
WEDNESDAY: "wed",
} as const;
type Weekday = (typeof Weekday)[keyof typeof Weekday];
Upvotes: 76
Reputation: 391
Here the example of how to convert a Typescript enum to a union type
export enum PaymentSystemEnum {
APPLE = 'APPLE',
GOOGLE = 'GOOGLE'
}
And it’s pretty easy
type = `${PaymentSystemEnum}`; // "APPLE" | "GOOGLE"
Upvotes: 2
Reputation: 29946
Typescript 4.8+ has some new features that we can use here. It works with string enums, number enums, and mixed enums too.
export type EnumToPrimitiveUnion<T> = `${T & string}` | ParseNumber<`${T & number}`>;
type ParseNumber<T> = T extends `${infer U extends number}` ? U : never;
export function typeCastEnum<E>(value: EnumToPrimitiveUnion<E>): E {
return value as E;
}
enum Enum1 {
A = 'a',
B = 'b',
X = 5,
}
enum Enum2 {
A = 'a',
C = 'c',
Y = 6,
}
enum Enum3 {
A2 = 'a',
B2 = 'b',
X2 = 5,
}
type PrimitiveEnum1 = EnumToPrimitiveUnion<Enum1>; // 'a' | 'b' | 5
typeCastEnum<Enum1>(Enum2.A); // ok, 'a' fits
typeCastEnum<Enum1>('b'); // ok
typeCastEnum<Enum1>(5); // ok
typeCastEnum<Enum1>(Enum3.A2 as Enum3); // ok, all values of Enum3 fit
// @ts-expect-error
typeCastEnum<Enum1>(Enum2.C); // err, 'c' does not fit
// @ts-expect-error
typeCastEnum<Enum1>('c'); // err, 'c' does not fit
// @ts-expect-error
typeCastEnum<Enum1>(Enum2.A as Enum2); // err, some values of Enum2 do not fit
// @ts-expect-error
typeCastEnum<Enum1>(Enum2.Y); // err
// @ts-expect-error
typeCastEnum<Enum1>(99); // err
// @ts-expect-error
typeCastEnum<Enum1>('foo'); // err
Upvotes: 1
Reputation: 330216
See TS4.1 ANSWER:
type WeekdayType = `${Weekday}`;
PRE TS-4.1 ANSWER:
This can't be done programmatically... you're trying to convert the type Weekday
, which is Weekday.MONDAY | Weekday.TUESDAY | Weekday.WEDNESDAY
, to the type WeekdayType
which is "mon" | "tue" | "wed"
. This conversion is a form of widening, since Weekday
is a subtype of WeekdayType
:
type WeekdayExtendsWeekdayType =
Weekday extends WeekdayType ? true : false
// type WeekdayExtendsWeekdayType = true
Unfortunately the compiler doesn't give you a handle to remove an "enum"-ness from the enum type and leave you with plain literal types.
So, workarounds? Maybe you don't actually need an enum
, but can make do with an object whose property values are string literals:
const lit = <V extends keyof any>(v: V) => v;
const Weekday = {
MONDAY: lit("mon"),
TUESDAY: lit("tue"),
WEDNESDAY: lit("wed")
}
type Weekday = (typeof Weekday)[keyof typeof Weekday],
If you inspect it, the value named Weekday
behaves like an enum object:
console.log(Weekday.TUESDAY); // tue
while the type named Weekday
behaves like the union of string values "mon" | "tue" | "wed"
that you were calling WeekdayType
:
const w: Weekday = "wed"; // okay
const x: Weekday = "xed"; // error
So in this workaround, there is no "enum"-ness, and therefore no need to distinguish the type Weekday
from the type WeekdayType
. It's a little different from an actual enum
(which includes types like Weekday.MONDAY
, which you'd have to represent as the cumbersome typeof Weekday.MONDAY
or create a different type alias for it), but it might behave similarly enough to be useful. Does that work for you?
Upvotes: 241
Reputation: 9735
With Typescript 4.1, it can be done!
enum Weekday {
MONDAY = 'mon',
TUESDAY = 'tue',
WEDNESDAY = 'wed'
}
type WeekdayType = `${Weekday}`;
And for number enums, thanks to @okku:
enum ThreeDigits {
ZERO = 0,
ONE = 1,
TWO = 2
}
type ThreeDigitsType = `${ThreeDigits}` extends `${infer T extends number}` ? T : never;
Upvotes: 70