keyof and Lookup Types

In JavaScript it is fairly common to have APIs that expect property names as parameters, but so far it hasn’t been possible to express the type relationships that occur in those APIs.

Enter Index Type Query or keyof;An indexed type query keyof T yields the type of permitted property names for T.A keyof T type is considered a subtype of string.

Example
  1. interface Person {
  2. name: string;
  3. age: number;
  4. location: string;
  5. }
  6. type K1 = keyof Person; // "name" | "age" | "location"
  7. type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...
  8. type K3 = keyof { [x: string]: Person }; // string

The dual of this is indexed access types, also called lookup types.Syntactically, they look exactly like an element access, but are written as types:

Example
  1. type P1 = Person["name"]; // string
  2. type P2 = Person["name" | "age"]; // string | number
  3. type P3 = string["charAt"]; // (pos: number) => string
  4. type P4 = string[]["push"]; // (...items: string[]) => number
  5. type P5 = string[][0]; // string

You can use this pattern with other parts of the type system to get type-safe lookups.

  1. function getProperty<T, K extends keyof T>(obj: T, key: K) {
  2. return obj[key]; // Inferred type is T[K]
  3. }
  4. function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {
  5. obj[key] = value;
  6. }
  7. let x = { foo: 10, bar: "hello!" };
  8. let foo = getProperty(x, "foo"); // number
  9. let bar = getProperty(x, "bar"); // string
  10. let oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"
  11. setProperty(x, "foo", "string"); // Error!, string expected number