how do I search this or'd type in the Record definition for a value

Viewed 85

The typescript docs here define a type to use as keys into a Record<>. I assume this is to limit and lock down the keys that can be used.

type CatName = "miffy" | "boris" | "mordred";

What kind of type is that? How do I search within it to see if it contains a string value? As in, how do I test it to see if "bart" is one of its "allowed" values?

Using the docs again, suppose the union type above is used as a key in a Record, like this:

const cats: Record<CatName, CatInfo> = {}

What if, at run time, we receive data on the wire for a different cat name and we try to create a record entry for that different cat name, as in 'bart'? This will generate an error, yes?

1 Answers

TypeScript's types are only for TypeScript compilation; your definitions will not help you check those values at runtime. If you want to do that, you'll need to define the list as an Array or Set.

const CAT_NAMES = ["miffy", "boris", "mordred"] as const;
type CatName2 = typeof CAT_NAMES[number];
  • as const is a const assertion. You're asserting to Typescript that CAT_NAMES will not change, so rather than a simple string[] it will type CAT_NAMES as readonly ["miffy", "boris", "mordred"].
  • typeof CAT_NAMES[number] will extract the types of the values of the passed array, which will help you get the CAT_NAMES list without repeating yourself.

You can then use your Record type to define the names as allowed keys. However, you might need to be a little bit careful here: If you've cleverly told TypeScript to assume that these values are part of a short list, then it might be confused and annoyed when you ask it to test that assumption and pass an arbitrary string. The same concerns apply if you were to type your wire format type definition to CatName but check their keys/values for arbitrary strings instead, though thankfully Object.keys returns string[].

function checkNameWithoutCast(name: string): name is CatName {
  // Error: you've passed the wrong type (string) to `includes`,
  // because `includes` *requires* ("miffy" | "boris" | "mordred")!
  return CAT_NAMES.includes(name);
}

function checkName(name: string): name is CatName {
  // You could also cast to string[], or cast `name` to CatName.
  return (CAT_NAMES as ReadonlyArray<string>).includes(name);
}

function yourFunction(name: string) {
  if (checkName(name)) {  // name is type (string)
    console.log(name);    // name is type ("miffy" | "boris" | "mordred")
  }
}
  • name is CatName2 is a type predicate. You could return a boolean instead, but by using a type predicate here TypeScript can narrow the type from string for you as I have in yourFunction.
  • Array.includes(...) and Array.indexOf(...) != -1 would work here, but if your list gets long, consider using a Set and Set.has to turn a linear-time O(n) operation into a logarithmic-time O(log n) or constant-time O(1) operation.

Playground Link

Related