Mapped Type Syntax
Mapped Type Syntax
Mapped types iterate over keys of an existing type to create a new type.
Basic Mapped Type
// Syntax: { [K in keyof T]: NewType }
type Optional<T> = {
[K in keyof T]?: T[K];
};
interface User {
id: number;
name: string;
email: string;
}
type OptionalUser = Optional<User>;
// { id?: number; name?: string; email?: string }
// Readonly mapped type
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type ReadonlyUser = Readonly<User>;
// { readonly id: number; readonly name: string; readonly email: string }
Modifiers in Mapped Types
// Remove readonly
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
// Remove optional
type Required<T> = {
[K in keyof T]-?: T[K];
};
// Remove both modifiers
type Clean<T> = {
-readonly [K in keyof T]-?: T[K];
};
Key Remapping
Key Remapping with as
Transforming Property Names
// Prefix all properties
type Prefixed<T, P extends string> = {
[K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
};
interface User {
name: string;
age: number;
}
type Getters = Prefixed<User, 'get'>;
// { getName: string; getAge: number }
// Filter properties by value type
type StringKeys<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type UserStrings = StringKeys<User>;
// { name: string }
Filtering with never
// Remove null and undefined from property values
type NonNullable2<T> = {
[K in keyof T]: T[K] extends null | undefined ? never : T[K];
};
// Pick only function properties
type FunctionProperties<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K];
};
interface Service {
name: string;
init(): void;
process(data: string): string;
destroy(): void;
version: number;
}
type ServiceMethods = FunctionProperties<Service>;
// { init: () => void; process: (data: string) => string; destroy: () => void }
Practical Mapped Types
Practical Mapped Types
Deep Partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? DeepPartial<T[K]>
: T[K];
};
interface Config {
database: {
host: string;
port: number;
credentials: {
user: string;
password: string;
};
};
cache: {
ttl: number;
maxEntries: number;
};
}
// All nested properties are optional
function updateConfig(patch: DeepPartial<Config>): void {
// ...
}
updateConfig({
database: {
port: 5432
}
});
PickByType
type PickByType<T, ValueType> = {
[K in keyof T as T[K] extends ValueType ? K : never]: T[K];
};
interface Mixed {
name: string;
age: number;
email: string;
active: boolean;
score: number;
}
type StringProps = PickByType<Mixed, string>;
// { name: string; email: string }
type NumberProps = PickByType<Mixed, number>;
// { age: number; score: number }
OmitByType
type OmitByType<T, ValueType> = {
[K in keyof T as T[K] extends ValueType ? never : K]: T[K];
};
type NoStrings = OmitByType<Mixed, string>;
// { age: number; active: boolean; score: number }
Template Literal Mapped Types
Template Literal Types with Mapped Types
Event Handler Types
type EventConfig = {
click: { x: number; y: number };
hover: { element: string };
focus: { target: HTMLElement };
};
// Generate handler types
type Handlers<T> = {
[K in keyof T as `on${Capitalize<string & K>}`]: (event: T[K]) => void;
};
type EventHandlers = Handlers<EventConfig>;
// {
// onClick: (event: { x: number; y: number }) => void;
// onHover: (event: { element: string }) => void;
// onFocus: (event: { target: HTMLElement }) => void;
// }
API Endpoints
type Route = '/users' | '/posts' | '/comments';
type ApiMethods = 'get' | 'post' | 'put' | 'delete';
type ApiClient = {
[M in ApiMethods]: {
[R in Route]: (data?: unknown) => Promise<unknown>;
};
};
// Or flattened:
type ApiClient2 = {
[K in `${ApiMethods}_${Route}`]: (data?: unknown) => Promise<unknown>;
};
// get_users, get_posts, post_users, post_posts, etc.
Best Practices
- Use mapped types to derive types from existing interfaces
- Combine with conditional types for filtering
- Use key remapping to transform property names
- Prefer mapped types over manual type definitions