TS Mapped Types
A mapped type derives a new type by iterating over every key of another. { [K in keyof T]: ... } — the type-level equivalent of Object.entries.
Basic mapped type
TS
type ReadonlyAll<T> = {
readonly [K in keyof T]: T[K];
};
type User = { id: number; name: string };
type FrozenUser = ReadonlyAll<User>;
// = { readonly id: number; readonly name: string }
Make all optional
TS
type OptionalAll<T> = {
[K in keyof T]?: T[K];
};
type PartialUser = OptionalAll<User>;
// = { id?: number; name?: string }
Modifier removal — -? and -readonly
TS
type Mutable<T> = { -readonly [K in keyof T]: T[K] };
type Required<T> = { [K in keyof T]-?: T[K] };
type R = Mutable<FrozenUser>; // back to User
type Q = Required<PartialUser>; // back to User
Key remapping with as (4.1+)
TS
type Getters<T> = {
[K in keyof T as `get${Capitalize<K & string>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// = { getId: () => number; getName: () => string }
Filtering keys
TS
type OnlyStrings<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type S = OnlyStrings<User>; // { name: string }
Building Record
TS
type Record<K extends keyof any, V> = {
[P in K]: V;
};
type Roles = Record<'admin' | 'user', boolean>;
// = { admin: boolean; user: boolean }
Tip: Combine mapped types with conditional types to build powerful transformations — but always check what your editor's hover shows. If the inferred type is unreadable, your future co-worker will struggle too.
Example
Example
type ReadonlyAll<T> = { readonly [K in keyof T]: T[K] };
type OptionalAll<T> = { [K in keyof T]?: T[K] };
type User = { id: number; name: string };
type ROUser = ReadonlyAll<User>;
type PartialU = OptionalAll<User>;
const u: PartialU = { name: 'Ada' };
console.log(u);
Try it Yourself »
Exercise
Modifier that REMOVES "?" from a mapped type.
{ [K in keyof T]
?: T[K] }
A single dash.
Discussion
Loading…