YouTip LogoYouTip

Ts Mapped Types

TypeScript Mapped Types |

Mapped Types are a powerful feature in TypeScript that allow creating new types based on existing ones.

It enables developers to batch modify property characteristics, such as making all properties optional or read-only.

Mapped types are the core technology behind TypeScript's built-in utility types.


Why Mapped Types Are Needed

In actual TypeScript development, we often need to create variants of existing types.

For example, we might want an optional version where all properties are optional, or a read-only version where all properties are read-only.

The traditional approach is to manually define these types, which is tedious and error-prone.

Mapped types provide a declarative way to automatically generate these type variants.

Concept Explanation: Mapped types use the keyof and in keywords to iterate through all keys of an existing type, then apply the same type transformation to each key.


Basic Mapped Types

Basic mapped types create new types by iterating over all keys of the original type.

This is the foundation for implementing utility types like Partial and Readonly.

Example

// Define user interface

interface User {

// User ID

 id: number;

// Username

 name: string;

// User email

 email: string;

}

// Implement Partial utility type

// Iterate over all keys of T, add optional modifier ?

 type Partial<T>={

// P iterates over all keys returned by keyof T

// T gets the value type of the corresponding key in the original type

?: T;

};

// Use Partial type

 type PartialUser = Partial<User>;

// PartialUser type is equivalent to:

// { id?: number; name?: string; email?: string }

// Can provide only partial properties

var user: PartialUser ={ name:"Alice"};

console.log("Partial user: "+ JSON.stringify(user));

Output:

Partial user: {"name":"Alice"}

Syntax Explanation: means iterate over all keys of type T. The ? modifier makes the property optional.


Property Modifiers

Mapped types support various property modifiers to alter property characteristics.

These modifiers can be combined to achieve different type transformation needs.

Example

// Define user interface

interface User {

// Username

 name: string;

// User age

 age: number;

}

// Use readonly mapping: make all properties read-only

// Add readonly modifier

 type Readonly<T>={

 readonly : T;

};

// Use optional mapping: make all properties optional (basic version)

 type Optional<T>={

?: T;

};

// Use -? mapping: remove optional modifier (make required)

// -? removes the existing ? modifier

 type Required<T>={

-?: T;

};

// Test read-only type

var readonlyUser: Readonly<User>={ name:"Alice", age:25};

// readonlyUser.age = 30; // Error: read-only property cannot be modified

// Test optional type

var optionalUser: Optional<User>={ name:"Bob"};

console.log("Read-only: "+ JSON.stringify(readonlyUser));

 console.log("Optional: "+ JSON.stringify(optionalUser));

Output:

Read-only: {"name":"Alice","age":25}Optional: {"name":"Bob"}

Modifier Explanation: ? adds optional, -? removes optional; readonly adds read-only, -readonly removes read-only.


Key Name Mapping

Mapped types can also use the as keyword to remap key names.

This is very useful when you need to uniformly change key name formats.

Example

// Define user interface

interface User {

// User ID

 id: number;

// Username

 name: string;

// User age

 age: number;

}

// Use as keyword to remap key names

// Add prefix to all keys

 type WithPrefix<T, Prefix extends string>={

// Use template literal types to rename keys

// Capitalize capitalizes the first letter

[P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T;

};

// Use WithPrefix to add "user" prefix

 type PrefixedUser = WithPrefix<User,"user">;

// Transformed type:

// { userId: number; userName: string; userAge: number }

// Use prefixed type

var user: PrefixedUser ={ userId:1, userName:"Alice", userAge:25};

console.log("Prefixed: "+ JSON.stringify(user));

Output:

Prefixed: {"userId":1,"userName":"Alice","userAge":25}

Template Literal Types: Using ${Prefix}${Capitalize} can dynamically generate new key names.


Key Filtering

By combining conditional types with mapped types, key filtering can be achieved.

This is very useful when implementing utility types like Omit.

Example

// Define user interface

interface User {

// User ID

 id: number;

// Username

 name: string;

// User password

 password: string;

// User email

 email: string;

}

// Implement Omit: exclude specified keys

// Use conditional types to filter keys

 type Omit<T, K extends keyof T>={

// P iterates over all keys of T

// If P can be assigned to K (i.e., in exclusion list), return never (exclude)

// Otherwise return P (keep the key)

[P in keyof T as P extends K ? never : P]: T;

};

// Use Omit to exclude password key

 type UserWithoutPassword = Omit<User,"password">;

// Transformed type:

// { id: number; name: string; email: string }

// Use type after excluding password

var user: UserWithoutPassword ={ id:1, name:"Alice", email:"a@b.com"};

console.log("No password: "+ JSON.stringify(user));

Output:

No password: {"id":1,"name":"Alice","email":"a@b.com"}

Never Type: Using never as a property type in a mapped type completely removes that property.


Conditional Mapping

Mapped types can be combined with conditional types to apply different transformations based on property types.

This makes type transformations more flexible and intelligent.

Example

// Define API response interface

interface APIResponse {

// Response data

 data: string;

// Error message

 error: string;

// Is loading

 isLoading:boolean;

// Timestamp

 timestamp: number;

}

// Convert function types to () => void

// Iterate over all properties, conditionally transform based on type

 type FunctionToVoid<T>={

// If T is a function type, convert to () => void

// Otherwise keep original type

: Textends(...args: any[])=> any

?()=>void

: T;

};

// Use conditional mapping

var response: FunctionToVoid<APIResponse>={

 data:"hello",

 error:"",

 isLoading:false,

 timestamp:Date.now()

};

console.log("Response: "+ JSON.stringify(response));

Output:

Response: {"data":"hello","error":"","isLoading":false,"timestamp":...}

Use Case: Conditional mapping is commonly used for handling API responses, cleaning configuration objects, and other scenarios requiring different processing based on types.


Built-in Mapped Types

TypeScript includes many utility types implemented using mapped types.

These utility types can meet most daily development needs.

Example

// Partial - make all properties optional

 type P1 = Partial<{ a: string; b: number }>;

// Result: { a?: string; b?: number }

// Required - make all optional properties required

 type R1 = Required<{ a?: string; b?: number }>;

// Result: { a: string; b: number }

// Readonly - make all properties read-only

 type RO1 = Readonly<{ a: string; b: number }>;

// Result: { readonly a: string; readonly b: number }

// Pick - select specified properties

 type PK = Pick<{ a: string; b: number; c:boolean},"a"|"b">;

// Result: { a: string; b: number }

// Omit - exclude specified properties

 type OM = Omit<{ a: string; b: number; c:boolean},"c">;

// Result: { a: string; b: number }

// Test Partial

 console.log("Partial: "+ JSON.stringify({} as P1));

// Test Pick

 console.log("Pick: "+ JSON.stringify({ a:"x"} as PK));

Utility Type Composition: These built-in utility types are all implemented using mapped types and conditional types. Understanding their principles helps use them better.


Notes

  • keyof keyword: Used to get a union type of all keys of a type
  • in keyword: Used to iterate over a union of key names
  • Modifier position: ? and readonly before property names indicate adding modifiers
  • Negative modifiers: -? and -readonly are used to remove modifiers
  • as keyword: Used to remap key names, must return string or numeric literal types

Advanced: Mapped types can be combined with conditional types and template literal types to implement complex type transformations.


Summary

Mapped types are one of the most powerful features in TypeScript's type system.

  • keyof: Get all keys of a type
  • in: Iterate over key names for mapping
  • ?: Add optional modifier
  • readonly: Add read-only modifier
  • -?: Remove optional modifier
  • as: Remap key names

Best Practice: Using mapped types effectively can significantly reduce repetitive type definitions and improve code maintainability.


← Ts Error HandlingTs Utility Types β†’