Mapped Types

#code/C4TwDgpgBAEg9gJwM7QLxQN4F8DcAoAegKgFoyBjAV2DJL1EigHkA7AGxACE442kBBFgBN4yCEijoMeKFADaAawggAXFCTAEASxYBzALpqARjzYQAhiygAfWIhT5cePOTgsNUVywBmiALZIaqwc3LwCwqIoElIyUEIQbGqalBAANLEIcEIsymre5nxpeLhAA)

type OnlyBoolsAndHorses = {
  [key: string]: boolean | Horse;
};

constconforms: OnlyBoolsAndHorses = {
del:true,
rodney:false,
};

A mapped type is a generic type which uses a union of PropertyKeys (frequently created via a keyof) to iterate through keys to create a type:

Try this code ↗

type OptionsFlags<Type> = {
  [PropertyinkeyofType]: boolean;
};

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

Try this code ↗

type Features = {
darkMode: () =>void;
newUserProfile: () =>void;
};

typeFeatureOptions = OptionsFlags<Features>;

type FeatureOptions = {
    darkMode: boolean;
    newUserProfile: boolean;
}

Mapping Modifiers

There are two additional modifiers which can be applied during mapping: readonly and ? which affect mutability and optionality respectively.

You can remove or add these modifiers by prefixing with - or +. If you don’t add a prefix, then + is assumed.

Try this code ↗

// Removes 'readonly' attributes from a type's properties
typeCreateMutable<Type> = {
  -readonly [PropertyinkeyofType]: Type[Property];
};

typeLockedAccount = {
readonlyid: string;
readonlyname: string;
};

typeUnlockedAccount = CreateMutable<LockedAccount>;

type UnlockedAccount = {
    id: string;
    name: string;
}

Try this code ↗

// Removes 'optional' attributes from a type's properties
typeConcrete<Type> = {
  [PropertyinkeyofType]-?: Type[Property];
};

typeMaybeUser = {
id: string;
name?: string;
age?: number;
};

typeUser = Concrete<MaybeUser>;

type User = {
    id: string;
    name: string;
    age: number;
}

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:

type MappedTypeWithNewProperties<Type> = {
    [PropertiesinkeyofTypeasNewKeyType]: Type[Properties]
}

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

Try this code ↗

type Getters<Type> = {
    [PropertyinkeyofTypeas`get${Capitalize<string&Property>}`]: () =>Type[Property]
};

interfacePerson {
name: string;
age: number;
location: string;
}

typeLazyPerson = Getters<Person>;

type LazyPerson = {
    getName: () => string;
    getAge: () => number;
    getLocation: () => string;
}

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

Try this code ↗

// Remove the 'kind' property
typeRemoveKindField<Type> = {
    [PropertyinkeyofTypeasExclude<Property, "kind">]: Type[Property]
};

interfaceCircle {
kind: "circle";
radius: number;
}

typeKindlessCircle = RemoveKindField<Circle>;

type KindlessCircle = {
    radius: number;
}

You can map over arbitrary unions, not just unions of string | number | symbol, but unions of any type:

Try this code ↗

type EventConfig<Events extends { kind: string }> = {
    [EinEventsasE["kind"]]: (event: E) =>void;
}

typeSquareEvent = { kind: "square", x: number, y: number };
typeCircleEvent = { kind: "circle", radius: number };

typeConfig = EventConfig<SquareEvent | CircleEvent>

type Config = {
    square: (event: SquareEvent) => void;
    circle: (event: CircleEvent) => void;
}

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:

Try this code ↗

type ExtractPII<Type> = {
  [PropertyinkeyofType]: Type[Property] extends { pii: true } ? true : false;
};

typeDBFields = {
id: { format: "incrementing" };
name: { type: string; pii: true };
};

typeObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;

type ObjectsNeedingGDPRDeletion = {
    id: false;
    name: true;
}
Last updated on