๐ŸŽ“ A1Learner
๐Ÿ”
0 of 37 lessons completed

TypeScript Key Remapping

Key remapping rewrites the keys of a mapped type with an as clause. The clause can rename each key, rebuild it from a template, or replace it with never to drop it.

Filtering keys by remapping to never

The conditional keeps the key when the member type is a string and replaces it with never otherwise. Only the string members survive the map.

type Profile = { userName: string; city: string; age: number; admin: boolean };
type StringFields<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K] };
const labels: StringFields<Profile> = { userName: 'Ada', city: 'Oslo' };
console.log(labels.userName);
console.log(labels.city);

Rebuilding keys as getter names

The template builds each new key from the old one, and Capitalize uppercases its first letter. Each member becomes a zero-argument function returning the original member type.

type Account = { userName: string; age: number };
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
const accountGetters: Getters<Account> = { getUserName: () => 'Ada', getAge: () => 36 };
console.log(accountGetters.getUserName());
console.log(accountGetters.getAge());

The old key name is gone

The remapped type holds getUserName and getAge only. Reading the original key name is refused, because that member was renamed away.

type Account = { userName: string; age: number };
type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
const accountGetters: Getters<Account> = { getUserName: () => 'Ada', getAge: () => 36 };
console.log(accountGetters.userName);

Example

type SignupForm = { email: string; age: number; city: string };
type StringGetters<T> = { [K in keyof T as T[K] extends string ? `get${Capitalize<string & K>}` : never]: () => T[K] };
const formGetters: StringGetters<SignupForm> = { getEmail: () => 'ada@example.com', getCity: () => 'Oslo' };
console.log(formGetters.getEmail());
console.log(formGetters.getCity());

Key points

  • The as clause sits after the key variable and computes the new key for each member.
  • Remapping a key to never removes that member from the result type.
  • Template literal types build new key names from the old key text.
  • A Getters<T> map renames each key into a getter name and types the member as a function returning the value.
  • Members outside the remapped key set do not exist on the result type.

In this example, the as clause does two jobs at once, filtering the number member to never and rebuilding the string keys as getter names. The result type holds exactly getEmail and getCity.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท TypeScript Playground

Exercise

How does a mapped type drop a member with key remapping?