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?