Mapped Types

TypeScript's mapped types offer a powerful mechanism for creating new types by manipulating the properties of existing types. This chapter delves into the world of mapped types, exploring their fundamentals, advanced usages, and best practices for effectively transforming object structures in your code.

Basic Mapped Types

Creating New Types from Existing Ones

Imagine you have an interface or type defining an object structure. Mapped types allow you to create a new type based on the original one, transforming its properties in a specific way.

				
					interface User {
  name: string;
  age: number;
}

// ReadonlyUser makes all properties readonly based on User
type ReadonlyUser = Readonly<User>;

const user: User = { name: "Alice", age: 30 };
const readonlyUser: ReadonlyUser = { name: "Bob", age: 35 };

// user.name = "Charlie"; // Valid: name is mutable in User
// readonlyUser.name = "David"; // Error: name is readonly in ReadonlyUser

				
			

Mapping Property Types

The core concept of mapped types lies in transforming the types of properties within the new type. Here, Readonly is used to make all properties readonly. You can define custom mappings for specific needs.

				
					interface Product {
  name: string;
  price: number;
}

// OptionalProduct makes all properties optional based on Product
type OptionalProduct = Partial<Product>;

const product: Product = { name: "Shirt", price: 20.5 };
const optionalProduct: OptionalProduct = { name: "Hat" }; // Only name provided, valid

// OptionalProduct allows omitting properties without errors

				
			

Key Remapping

Renaming Properties During Mapping

Mapped types allow you to not only transform property types but also rename them during the creation of the new type.

				
					interface User {
  name: string;
  age: number;
}

// UserDetails renames name to fullName and adds email property
type UserDetails = {
  fullName: User["name"]; // Accessing property type from User
  email: string;
};

const userDetails: UserDetails = {
  fullName: "Charlie Brown",
  email: "charlie@example.com"
};

				
			

Combining Renaming and Type Transformation

You can combine renaming with type transformations for more complex manipulations.

				
					interface Product {
  name: string;
  price: number;
}

// DiscountedProduct renames price to salePrice (string) with a discount applied
type DiscountedProduct = {
  name: Product["name"];
  salePrice: string; // New property type (string)
};

function getDiscountedPrice(product: Product, discount: number): string {
  return (product.price * (1 - discount)).toFixed(2);
}

const product: Product = { name: "Shirt", price: 20.5 };
const discountedProduct: DiscountedProduct = {
  name: product.name,
  salePrice: getDiscountedPrice(product, 0.1) // Applying discount
};

console.log(discountedProduct.salePrice); // Output: "18.45"

				
			

Conditional Mapped Types

Transforming Based on Conditions

Mapped types can leverage conditional logic to create new types based on the properties or values within the original type.

				
					interface User {
  name: string;
  role: "admin" | "editor" | "user";
}

// Permissions type with conditional property based on role
type Permissions = {
  [key: string]: boolean;
  canEdit?: boolean; // Optional for non-admin roles
  canDelete?: boolean; // Only for admin role
} & { [P in keyof User]: User[P] }; // Keep original properties

const adminUser: User & Permissions = {
  name: "Alice",
  role: "admin",
  canEdit: true,
  canDelete: true
};

const editorUser: User & Permissions = {
  name: "Bob",
  role: "editor",
  canEdit: true
};

// Error: canDelete is not allowed for non-admin roles
// const nonAdminUser: User & Permissions = {
//   name: "Charlie",
//   role: "user",
//   canDelete: false
// };

				
			

Enhancing Type Safety with Conditions

Conditional mapped types add another layer of type safety by ensuring properties only exist or have specific types based on conditions within the original type. This helps prevent runtime errors and improves code reliability.

Advanced Mapped Types

Intersection with Mapped Types

Combining Mapped Types with Existing Interfaces

Mapped types can be combined with existing interfaces to create new types that inherit properties from both.

				
					interface Product {
  name: string;
  price: number;
}

// DiscountedProduct with price discount and readonly properties
type DiscountedProduct = Readonly<Product & {
  salePrice: string; // New property from mapping
}>;

const product: Product = { name: "Hat", price: 15 };
const discountedProduct: DiscountedProduct = {
  name: product.name, // Inheriting from Product
  price: product.price, // Inheriting from Product (readonly)
  salePrice: "13.50" // New property
};

// discountedProduct.price = 10; // Error: price is readonly

				
			

Achieving Flexibility with Intersection

This approach allows you to define a base structure with an interface and then create variations using mapped types for specific scenarios like discounts or read-only access.

Mapped Types with Utility Types

Leveraging Existing Utility Types

TypeScript provides built-in utility types like Partial, Pick, and Record that can be used within mapped types for common transformations.

				
					interface User {
  name: string;
  age: number;
  email: string;
}

// UserSummary picks only name and email properties
type UserSummary = Pick<User, "name" | "email">;

const user: User = { name: "David", age: 32, email: "david@example.com" };
const userSummary: UserSummary = { name: user.name, email: user.email };

// LoginCredentials picks only email for login purpose (using Record)
type LoginCredentials = Record<"email", string>;

const loginCredentials: LoginCredentials = { email: user.email };

				
			

Enhancing Code Readability and Maintainability

Utilizing existing utility types within mapped types improves code readability and maintainability by leveraging established functionality for common transformations.

When to Use Mapped Types

Transforming Object Structures

Mapped types are ideal for scenarios where you need to create new types based on existing ones, modifying property types, renaming properties, or applying conditional logic for property existence.

Code Reusability and Maintainability

By defining generic mapped types, you can create reusable transformations that can be applied to various object structures, promoting code reusability and maintainability.

Best Practices for Mapped Types

  • Clarity and Readability: Use clear and descriptive names for your mapped types and the logic within them. Consider adding comments to explain the purpose and transformation applied.
  • Complexity Management: While mapped types are powerful, strive for a balance between functionality and complexity. Explore alternative approaches if simpler solutions exist.
  • Testing: Thoroughly test your code using various object structures to ensure mapped types function as expected and handle edge cases appropriately.

Considerations

  • Advanced Generics: Explore advanced generics concepts like mapped types with generics and conditional types for even more flexible and powerful object structure transformations.
  • Utility Type Creation: Consider creating custom utility types based on mapped types to encapsulate common transformations within your codebase.

Mapped types provide a versatile tool for manipulating object structures and creating new types in TypeScript. By understanding their capabilities, limitations, and best practices, you can leverage them effectively to write cleaner, more maintainable, and type-safe code for various scenarios. Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India