typescriptenums

Convert a union string to an enum


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?


Solution

  • You could infer it like that (we infer the enum's key rather):

    Playground

    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