1

I have a const enum like the following:

const enum Color {
    Red = "red",
    Green = "green",
    Blue = "blue"
}

I also have some string (say, colorString), coming from outside. Its value is supposed to be either "red", or "green", or "blue", but it's not for sure. I'd like to validate it and throw in case of the invalid value.

For 'conversion', I could do just:

const color: Color = colorString as Color;

This 'converts' string to enum, but doesn't validate it. If constString === "purple" there will be no error at runtime. For validation, I could do:

function stringToColor(colorString: string): Color {
    switch (colorString) {
        case Color.Red:
        case Color.Green:
        case Color.Blue:
            return colorString; // no need to do 'as', TS knows that colorString is valid Color enum value
        default:
            throw new Error(`Invalid color ${colorString}.`);
    }
}

This 'converts' string to enum and validates it, but it's kinda verbose and error-prone. E.g., if I add new value to the Color enum, I'll have to add new case statement to the switch in the stringToColor function in order for my code to operate properly.

Afaik, const enums exist only at compile time, and are completely erased to plain strings in runtime. So there is no any real conversion. Plus, I can't do Object.values(Color), because Color is const enum and doesn't exist at runtime. What I actually need is:

  • to check if the string has one of designated (valid) values - at runtime;
  • to tell TypeScript that string can be represented as enum - at compile time.

Another solution is to keep all valid values in an object like this:

const colors: { readonly [color in Color]: color } = {
    [Color.Red]: Color.Red,
    [Color.Green]: Color.Green,
    [Color.Blue]: Color.Blue,
};

The good part here is that this object must contain property for every enum value (unlike Color[]). If I add a new value to enum, but forget to add it to the colors object, if will fail at compile time. So it's less error-prone, than just writing a dozen of case statements. Then I could do:

function stringToColor(colorString: string): Color | undefined {
    return Object.values(colors).find((value) => colorString === value);
}

It returns undefined in case of invalid value, which is acceptable.

However, I wonder if there are less verbose and more elegant solutions out there.

axmrnv
  • 964
  • 10
  • 23

1 Answers1

0

If you're using Color enum somewhere else in your program and want to stick with using it then unfortunately there's no elegant solution for what you're trying to do. take a look at here.

But, there is one almost elegant way to achieve what you want to achieve, which is to use an object like below:

const Color = {
    Red: "red",
    Green: "green",
    Blue: "blue"
} as const


const func = (color: typeof Color[keyof typeof Color]) => {
  console.log(color);
}

func("blue") // okay!
func("purple") // Argument of type '"purple"' is not assignable to parameter of type '"red" | "green" | "blue"'

The downside is that you can't use Color as a type like enums, for that you have to use typeof Color.

Mahdi Ghajary
  • 2,717
  • 15
  • 20
  • Thanks for the answer, but that's not what I want to achieve. What I want to achieve is a function that receives a `string` (just an ordinary string) and returns a `Color` (doesn't matter if it's an `Object` or a `const enum`) if the string is valid and throws or returns `undefined` if the string is not valid. Could you please provide an example of a function like that? The function you've provided is no good. – axmrnv Oct 18 '20 at 21:47
  • @axmrnv As I wrote in my answer, what you want to do is not currently doable using enums, you must make tradeoffs, things you can do: 1- write a union type and enum and be careful they're always consistent. 2- use enum just like you did and perform runtime checks 3- do what I did in my asnwer. There is no neat answer for your need unfortunately. – Mahdi Ghajary Oct 19 '20 at 08:52