Skip to main content

Module 17: Mapped Types

Mapped types allow you to transform existing types by iterating over their properties. They're essential for creating flexible, reusable type transformations.


1. Basic Mapped Type

type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

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

type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; }

2. Optional Mapped Type

type Optional<T> = {
[P in keyof T]?: T[P];
};

type OptionalUser = Optional<User>;
// { name?: string; age?: number; }

3. Nullable Mapped Type

type Nullable<T> = {
[P in keyof T]: T[P] | null;
};

type NullableUser = Nullable<User>;
// { name: string | null; age: number | null; }

4. Mapping Modifiers

Adding Modifiers

type Required<T> = {
[P in keyof T]-?: T[P]; // Remove optional
};

type Mutable<T> = {
-readonly [P in keyof T]: T[P]; // Remove readonly
};

5. Type Transformations

type Getters<T> = {
[P in keyof T as `get${Capitalize<string & P>}`]: () => T[P];
};

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

type UserGetters = Getters<User>;
// {
// getName: () => string;
// getAge: () => number;
// }

6. Conditional Property Types

type StringProperties<T> = {
[P in keyof T]: T[P] extends string ? T[P] : never;
};

type ExtractStrings<T> = Pick<T, {
[P in keyof T]: T[P] extends string ? P : never;
}[keyof T]>;

7. Key Remapping

type EventHandlers<T> = {
[P in keyof T as `on${Capitalize<string & P>}Change`]: (value: T[P]) => void;
};

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

type FormHandlers = EventHandlers<Form>;
// {
// onNameChange: (value: string) => void;
// onEmailChange: (value: string) => void;
// onAgeChange: (value: number) => void;
// }

8. Deep Mapped Types

type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};

interface Config {
database: {
host: string;
port: number;
};
}

type ReadonlyConfig = DeepReadonly<Config>;

9. Filtering Properties

type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];

type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;

interface Mixed {
name: string;
greet(): void;
age: number;
sayBye(): void;
}

type Functions = FunctionProperties<Mixed>;
// { greet: () => void; sayBye: () => void; }

10. Real-World Example: Form Validation

type ValidationRules<T> = {
[P in keyof T]?: {
required?: boolean;
minLength?: number;
maxLength?: number;
pattern?: RegExp;
custom?: (value: T[P]) => boolean;
};
};

interface RegisterForm {
username: string;
email: string;
password: string;
age: number;
}

const rules: ValidationRules<RegisterForm> = {
username: {
required: true,
minLength: 3,
maxLength: 20
},
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
password: {
required: true,
minLength: 8
}
};

Key Takeaways

✅ Mapped types iterate over properties
✅ Use modifiers to add/remove readonly and optional
Key remapping transforms property names
✅ Combine with conditional types for filtering
Deep mapping for nested structures


Practice Exercises

Exercise 1: Create Setters Type

type Setters<T> = {
[P in keyof T as `set${Capitalize<string & P>}`]: (value: T[P]) => void;
};

Exercise 2: Promise Wrapper

type Promisify<T> = {
[P in keyof T]: T[P] extends (...args: any[]) => infer R
? (...args: Parameters<T[P]>) => Promise<R>
: T[P];
};

Next Steps

In Module 18, we'll explore Conditional Types for advanced type logic and transformations.