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.