Reputation: 60006
Suppose I have this string enum:
enum Color {
None = "Q",
Red = "R",
Green = "G",
Blue = "B",
}
Now, in TypeScript, string enums have no reverse mappings, so I can't write Color["Q"]
to obtain a potential Color
back. I tried declaring my own helper function:
type StringEnum = {[key: string]: string};
function lookup<E extends StringEnum>(stringEnum: E, s: string): keyof E | undefined {
for (const enumValue of keysOf(stringEnum)) {
if (stringEnum[enumValue] === s) {
return enumValue;
}
}
return undefined;
}
with keysOf
being
function keysOf<K extends {}>(o: K): (keyof K)[];
function keysOf(o: any) { return Object.keys(o); }
… but actually, I don't know what the return type should be. This is meant to compile, but doesn't:
const color: Color = lookup(Color, "Q") || Color.None;
because the return type of the lookup
function is now "None" | "Red" | "Green" | "Blue" | undefined
instead of just Color | undefined
.
Can this be solved well while preserving the type information?
Upvotes: 3
Views: 2177
Reputation: 74710
Your lookup
function returns an Enum string key, but you also provide the Enum value Color.None
as short-circuit in case lookup
returns undefined. So the types don't match here.
In general, string Enum keys can be typed like this:
type ColorKeys = keyof typeof Color // "None" | "Red" | "Green" | "Blue"
const blueKey: keyof typeof Color = "Blue"
If you write const blueValue = Color.None
, variable blueValue
holds the Enum string value "Q"
, not the literal "None"
. So we can fix the const color
assignment in the following way:
const color = lookup(Color, "Q") || "None";
// or with explicit type
const color: keyof typeof Color = lookup(Color, "Q") || "None";
Enum types cannot have an index signature, so I typed StringEnum
as {[key: string]: any}
here to make it compile.
lookup
If you want to pass in an unnarrowed string value to lookup
and give it back as narrowed enum value type if existent (otherwise undefined
), you can do it in a similar way to your first example:
function lookup<E extends StringEnum>(
stringEnum: E,
s: string
): E[keyof E] | undefined {
for (const enumKey of keysOf(stringEnum)) {
if (stringEnum[enumKey] === s) {
// here we have to help the compiler
return stringEnum[enumKey] as E[keyof E];
}
}
return undefined;
}
// let's test it
const look = lookup(Color, "Q") // const look: Color | undefined
// for further understanding
type AllColorKeys = keyof typeof Color // = "None" | "Red" | "Green" | "Blue"
type AllColorValues = (typeof Color)[keyof typeof Color] // = Color
type IsColorSuperType = Color.Blue extends Color ? true: false // = true
In case of Color
enum, the returned lookup type will give you back value Q
with type Color | undefined
, as the union of all possible Color
enum values Color.None
, Color.Red
and so on will be of supertype Color
. The defined enum construct itself has the type typeof Color
, comparable to the static and instance side of a class.
Upvotes: 4