Advanced mapped type patterns and techniques.
Key Remapping with Filters
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface User {
name: string;
age: number;
}
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; }
Filter Properties by Type
type StringProperties<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface User {
name: string;
age: number;
email: string;
}
type UserStrings = StringProperties<User>;
// { name: string; email: string; }
Remove Properties
type OmitByType<T, U> = {
[K in keyof T as T[K] extends U ? never : K]: T[K];
};
interface User {
name: string;
age: number;
email: string;
}
type UserWithoutStrings = OmitByType<User, string>;
// { age: number; }
Rename Properties
type Rename<T, K extends keyof T, N extends string> = Omit<T, K> & {
[P in N]: T[K];
};
interface User {
firstName: string;
lastName: string;
}
type Renamed = Rename<User, "firstName", "first">;
// { first: string; lastName: string; }
Deep Readonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
interface User {
name: string;
address: {
street: string;
city: string;
};
}
type ReadonlyUser = DeepReadonly<User>;
// {
// readonly name: string;
// readonly address: {
// readonly street: string;
// readonly city: string;
// };
// }
Optionalize Properties
type Optionalize<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
interface User {
name: string;
age: number;
email: string;
}
type PartialEmail = Optionalize<User, "email">;
// { name: string; age: number; email?: string; }
Map Values
type MapValues<T, F> = {
[K in keyof T]: F extends (value: T[K]) => infer R ? R : never;
};
interface User {
name: string;
age: number;
}
type Stringified = MapValues<User, (v: any) => string>;
// { name: string; age: string; }
Exercises
Exercise 1: Key Transformation
// Create a mapped type that:
// - Transforms all keys to lowercase
// - Preserves values
Exercise 2: Filter and Transform
// Create a mapped type that:
// - Filters out function properties
// - Transforms remaining properties to optional
Exercise 3: Deep Optional
// Create a type 'DeepPartial' that:
// - Makes all properties optional recursively
// - Works with nested objects and arrays
Exercise 4: Property Prefix
// Create a mapped type that:
// - Adds a prefix to all property names
// - Example: { name: string } => { user_name: string }
Exercise 5: Complex Transformation
// Create a mapped type that:
// - Converts all string properties to number
// - Converts all number properties to string
// - Leaves other types unchanged
Best Practices
- Use key remapping - Transform keys when needed
- Filter properties - Use
as neverto remove properties - Combine transformations - Chain mapped types
- Test with real types - Ensure they work correctly
- Document complex types - Explain the transformation
Common Mistakes
- Forgetting keyof in key remapping
// ❌ Wrong
type Bad<T> = {
[K in T]: T[K]; // T is not a union type
};
// ✅ Correct
type Good<T> = {
[K in keyof T]: T[K];
};
- Wrong filter syntax
// ❌ Wrong
type Bad<T> = {
[K in keyof T]: T[K] extends string ? T[K] : never;
};
// ✅ Correct - use 'as never' to filter
type Good<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
- Not handling nested types
// ❌ Only one level
type Shallow<T> = {
[K in keyof T]?: T[K];
};
// ✅ Recursive
type Deep<T> = {
[K in keyof T]?: T[K] extends object ? Deep<T[K]> : T[K];
};
Next Steps
Learn about Template Literal Types.