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

TypeScript Mapped Types

A mapped type builds a new object type by walking the keys of an existing type. The syntax repeats one rule for every key it visits.

A home-grown MyPartial matches Partial

MyPartial makes every member optional with one mapped rule. A patch with missing members is accepted, just as with Partial.

interface AccountSettings {
  theme: string;
  pageSize: number;
  showTips: boolean;
}
type MyPartial<T> = { [K in keyof T]?: T[K] };
const patchA: MyPartial<AccountSettings> = { pageSize: 20 };
const patchB: Partial<AccountSettings> = { pageSize: 20 };
console.log(patchA.pageSize);
console.log(patchB.pageSize);
console.log(patchA.theme);

A flags type from the same keys

The mapped type keeps the keys of the source and gives every member a boolean value. The result tracks which fields were touched.

type Flags<T> = { [K in keyof T]: boolean };
interface FormFields {
  title: string;
  body: string;
}
const touched: Flags<FormFields> = { title: true, body: false };
console.log(touched.title);
console.log(touched.body);

Removing ? completes a draft type

The draft type marks every member optional. The mapped type with -? removes that marker, so a complete value is required.

interface DraftProfile {
  displayName?: string;
  bio?: string;
}
type Complete<T> = { [K in keyof T]-?: T[K] };
const finished: Complete<DraftProfile> = {
  displayName: "Asha",
  bio: "Writer",
};
console.log(finished.displayName);
console.log(finished.bio);

Example

type MyReadonly<T> = { readonly [K in keyof T]: T[K] };
interface LabelConfig {
  text: string;
  size: number;
}
const labelCfg: MyReadonly<LabelConfig> = { text: "Sale", size: 12 };
console.log(labelCfg.text);
console.log(labelCfg.size);

Key points

  • A mapped type loops over the keys of a source type to build a new type.
  • keyof T gives the union of key names of T, and the mapped type visits each one.
  • Adding ? in a mapped type makes every visited member optional.
  • Writing -? removes optionality, and writing -readonly removes the readonly guard.

In this example, MyReadonly adds readonly to every member of LabelConfig, matching the behaviour of the built-in Readonly type.

Note

The built-in Partial and Readonly types use this same mapped pattern.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท TypeScript Playground

Exercise

What does the mapped type { [K in keyof T]?: T[K] } produce?