Narrowing string to string literal union

Viewed 6622

I want to narrow a string to a string literal union. In other words, I want to check if the string is one of the possible values of my literal union, so that this will work (if the operator couldbe existed).

type lit = "A" | "B" | "C";
let uni: lit;
let str = "B";
if(str couldbe lit){
    uni = str;
} else {
    doSomething(str);
}

How can I achieve this?

I tried using if (str instanceof lit), but that doesn't seem to work. Using keyof to iterate over the string union doesn't work either, because the allowed values aren't keys per se.

One way would be to use switch with one case for each possible value, but that could lead to subtle errors if lits allowed values change.

3 Answers

If you hate switch cases, as I do:
since TypeScript 3.4 – const assertions it's also possible to produce union type from array of your strings ^_^

const list = <const>["A", "B", "C"];
type Lit = typeof list[number]; // "A" | "B" | "C"

function isLit(str: string): str is Lit {
  return !!lits.find((lit) => str === lit);
}

This is my take on the problem with the type guard and with strictNullChecks turned off (this is limitation on a project; if this option is true TS will require exhaustiveness on the switch/case).

Line const _notLit: never = maybeLit; guaranties that when you change lit type you need to update the switch/case also.

Downside of this solution is that it gets very verbose as the union type lit grows.

type lit = "A" | "B" | "C";

function isLit(str: string): str is lit {
  const maybeLit = str as lit;
  switch (maybeLit) {
    case "A":
    case "B":
    case "C":
      return true;
  }

  // assure exhaustiveness of the switch/case
  const _notLit: never = maybeLit;

  return false;
}

If possible this task is more suitable for enum or if you require a type and don't mind creating underlying enum for checking, you can create type guard something like this:

enum litEnum {
  "A",
  "B",
  "C",
}
type lit = keyof typeof litEnum;

function isLit(str: string): str is lit {
  return litEnum[str] !== undefined;
}
Related