Mapped Types in TypeScript

In TypeScript, mapped types are a powerful feature that allows developers to create new types by iterating over the properties of existing ones. Utilizing the in keyword and the keyof operator, mapped types enable transformations such as making properties readonly, creating partial types, picking specific properties, and applying conditions to generate variations based on the original type. This concise syntax facilitates the creation of more expressive and reusable type definitions, reducing redundancy and improving code maintainability.

Mapped types prove particularly useful when dealing with scenarios where consistent transformations or variations of types are required, providing a flexible and efficient tool for enhancing the expressiveness and robustness of TypeScript code.

Here are several examples illustrating the usage of mapped types:

Readonly Mapped Type

interface User { name: string; age: number; } type ReadonlyUser = { readonly [K in keyof User]: User[K]; }; const user: ReadonlyUser = { name: "John", age: 30 }; // user.name = "Doe"; // Error: Cannot assign to 'name' because it is a read-only property

Partial Mapped Type

interface User { name: string; age: number; } type PartialUser = { [K in keyof User]?: User[K]; }; const partialUser: PartialUser = { name: "John" };

Pick Mapped Type

interface User { name: string; age: number; } type PickUser = { [K in "name" "age"]: User[K]; }; const pickUser: PickUser = { name: "John", age: 30 };

Record Mapped Type

type RecordUser = { [K in "name" "age"]: string; }; const recordUser: RecordUser = { name: "John", age: "30" };

Conditional Mapped Type

interface User { name: string; age: number; } type StringProperties<T> = { [K in keyof T]: T[K] extends string ? K : never; }; type OnlyStrings = StringProperties<User>; // OnlyStrings is { name: "name" }

Mapped Type for Function Properties

interface User { name: string; age: number; } type FunctionPropsToOptional<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never; }; type OptionalFunctionProps = FunctionPropsToOptional<User>; // OptionalFunctionProps is { name: never, age: never }

Mapping over Union Types

type MapUnion<T extends string number symbol> = { [K in T]: K; }; type Color = "red" "blue" "green"; type MappedColors = MapUnion<Color>; // MappedColors is { red: "red", blue: "blue", green: "green" }

Dynamically Creating Types

type DynamicType<T> = { [K in keyof T]: T[K] null; }; interface Example { name: string; age: number; } type NullableExample = DynamicType<Example>; // NullableExample is { name: string null, age: number null }

Conclusion

Mapped types in TypeScript offer a versatile mechanism for transforming and creating new types based on existing ones, providing enhanced flexibility and reducing redundancy in type definitions. They are commonly used in scenarios where you need to generate variations of types, ensuring consistency and correctness in the codebase.