typescriptunion-typesdiscriminated-union

Checking validity of string literal inside union type at runtime?


I have a type Command that encapsulates a set of actions which I discriminate using a property called action like for example:

type Command = 
  { action: "run", x: number, y: number } |
  { action: "jump", height: number } |
  { action: "attack", weapon: "sword" | "knife"}; //... more actions

As you can see, all commands have action but the rest of the properties are different. When I receive the command, type narrowing works as expected to difference between them. The problem is, if I receive a command from the user, how can I validate if its a valid action? something like (pseudocode)

let userCommand: string; // unknown string coming from the user
if (!userCommand in Command.action){ // something like this
    throw `Command "${userCommand}" is unknown`;
}

I know types don't exist at runtime so I don't know if there is a way to check other than manually validating the string against all possible actions duplicating them in the type declaration and in the validation. I found this SO post which covers the same problem when you have a literal union type of strings, but in my case this does not work as the string is part of a bigger type. I don't know if there is a way to validate this, or maybe a better way to design my types so that I can validate this at runtime.


Solution

  • We can use a enum to define the valid values of action.

    enum CommandAction {
      RUN = "run",
      JUMP = "jump",
      ATTACK = "attack",
    }
    
    type Command =
      { action: CommandAction.RUN, x: number, y: number } |
      { action: CommandAction.JUMP, height: number } |
      { action: CommandAction.ATTACK, weapon: "sword" | "knife" }; //... more actions
    
    let userCommand: keyof typeof CommandAction = 'RUN'; // unknown string coming from the user
    if (!CommandAction[userCommand]) { // something like this
      throw `Command "${userCommand}" is unknown`;
    }
    

    Typescript Playground