I have a situation where from upstream I get a union string, but downstream needs to use enums to express enumerated types.
The types happen to overlap perfectly, but I need a way to treat them as the same.
type ColorUnionString = "Red" | "Green" | "Blue" ;
enum Color {
Red = "Red",
Green = "Green",
Blue = "Blue"
}
function processUnionString(value: ColorUnionString) : Color {
//Type 'ColorUnionString' is not assignable to type 'Color'.
// Type '"Red"' is not assignable to type 'Color'.(2322)
return value;
}
I could just coerce the type like:
function processUnionString(value: ColorUnionString) : Color {
return value as Color;
}
But the problem is later, if we changed this:
- type ColorUnionString = "Red" | "Green" | "Blue" ;
+ type ColorUnionString = "Red" | "Green" | "Blue" | "Purple" ;
Then we would not see a type error.
I'm looking for a generic solution. I have many such enumerated types.
My attempt at a generic unionStringToEnumValue
is as follows:
enum Color {
Red = "Red",
Green = "Green",
Blue = "Blue"
}
type ColorUnionString = "Red" | "Green" | "Blue" ;
function processUnionString(value: ColorUnionString) : Color {
return unionStringToEnumValue<Color, typeof Color>(Color, value);
}
export function unionStringToEnumValue<
T_ENUM_AS_TYPE,
T_ENUM_AS_VALUE extends Record<string, T_ENUM_AS_TYPE>
>(
theEnum: T_ENUM_AS_VALUE,
value: keyof T_ENUM_AS_VALUE,
): T_ENUM_AS_TYPE {
const enumValues = Object.values(theEnum) as string[];
if (!enumValues.includes(value as string)) {
throw new Error(`Value: ${value as string} did not match one of the enum values ${JSON.stringify(theEnum)}`);
}
return value as T_ENUM_AS_TYPE;
}
This works, but it's a bit verbose - you have to declare the generic parameters, otherwise the return type is always unknown
.
Why is the return type unknown
if the generic parameters are not provided, and is there a way to have the generic parameters be inferred, from the first argument?
You could infer it like that (we infer the enum's key rather):
enum Color {
Red = "Red",
Green = "Green",
Blue = "Blue"
}
type ColorUnionString = "Red" | "Green" | "Blue";
function processUnionString<T extends ColorUnionString>(value: T) {
return unionStringToEnumValue(Color, value);
}
declare function unionStringToEnumValue<K extends string, E extends Record<K, unknown>>(theEnum: E, value: K ): E[K];
const red = processUnionString('Red'); // const red: Color.Red