Warning: This page is a work in progress

Mapped Types

When you don’t want to repeat yourself, sometimes a type needs to be based on another type.

Mapped types build on the syntax for index signatures, which are used to declare the types of properties which has not been declared ahead of time:

type OnlyBoolsAndHorses = { [key: string]: boolean | Horse; }; const conforms: OnlyBoolsAndHorses = { del: true, rodney: false, };Try

A mapped type is a generic type which uses a union created via a keyof to iterate through the keys of one type to create another:

type OptionsFlags<Type> = { [Property in keyof Type]: boolean; };Try

In this example, OptionFlags will take all the properties from the type Type and change their values to be a boolean.

type FeatureFlags = { darkMode: () => void; newUserProfile: () => void; }; type FeatureOptions = OptionsFlags<FeatureFlags>; // ^ = type FeatureOptions = { // darkMode: boolean; // newUserProfile: boolean; // }Try

Mapping Modifiers

There are a two additional modifiers which can be applied during mapping: readonly and ? which affect mutability and optionality respectively. Both of these modifiers support a prefix of - or + with + being the default.

ts
type CreateMutable<Type> = { -readonly [Property in keyof Type]: Type[Property]; }; type LockedAccount = { readonly id: string; readonly name: string; }; type UnlockedAccount = CreateMutable<LockedAccount>; // ^?
type Concrete<Type> = { [Property in keyof Type]-?: Type[Property]; }; type MaybeUser = { id: string; name?: string; age?: number; }; type User = Concrete<MaybeUser>; // ^ = type User = { // id: string; // name: string; // age: number; // }Try

Key Remapping via as

In TypeScript 4.1 and onwards, you can re-map keys in mapped types with an as clause in a mapped type:

ts
type MappedTypeWithNewProperties<Type> = { [Properties in keyof Type as NewKeyType]: Type[Properties] }

You can leverage features like template literal types to create new property names from prior ones:

type Getters<Type> = { [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property] }; interface Person { name: string; age: number; location: string; } type LazyPerson = Getters<Person>; // ^ = type LazyPerson = { // getName: () => string; // getAge: () => number; // getLocation: () => string; // }Try

You can filter out keys by producing never via a conditional type:

// Remove the 'kind' property type RemoveKindField<T> = { [K in keyof T as Exclude<K, "kind">]: T[K] }; interface Circle { kind: "circle"; radius: number; } type KindlessCircle = RemoveKindField<Circle>; // ^ = type KindlessCircle = { // radius: number; // }Try

Further Exploration

Mapped types work well with other features in this type manipulation section, for example here is a mapped type using a conditional type which returns either a true or false depending on whether an object has the property pii set to the literal true:

ts
type ExtractPII<Type> = { [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false; }; type DBFields = { id: { format: "incrementing" }; name: { type: string; pii: true }; }; type ObjectsNeedingGDPRDeletion = CreateMutable<DBFields>;

The TypeScript docs are an open source project. Help us improve these pages by sending a Pull Request

Contributors to this page:
OTOrta Therox  (4)

Last updated: Jan 19, 2021