DEV Community

Atlas Whoff
Atlas Whoff

Posted on

TypeScript Mapped Types: Transform Objects at the Type Level

TypeScript Mapped Types: Transform Objects at the Type Level

Mapped types let you create new types by transforming existing ones. They're the building blocks behind Partial, Required, Readonly, and your own custom utilities.

The Syntax

// Basic mapped type: iterate over keys
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type User = { id: string; name: string; age: number };
type NullableUser = Nullable<User>;
// { id: string | null; name: string | null; age: number | null }
Enter fullscreen mode Exit fullscreen mode

Built-In Utilities (All Use Mapped Types)

// How Partial is implemented
type Partial<T> = { [K in keyof T]?: T[K] };

// How Required is implemented
type Required<T> = { [K in keyof T]-?: T[K] }; // -? removes optional

// How Readonly is implemented
type Readonly<T> = { readonly [K in keyof T]: T[K] };

// How Pick is implemented
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
Enter fullscreen mode Exit fullscreen mode

Practical: Form State

type FormState<T> = {
  values: T;
  errors: Partial<Record<keyof T, string>>;
  touched: Partial<Record<keyof T, boolean>>;
};

type UserForm = FormState<{ name: string; email: string; password: string }>;
// Automatically creates typed errors and touched for each field
Enter fullscreen mode Exit fullscreen mode

Conditional Mapped Types

// Only make function properties optional
type OptionalMethods<T> = {
  [K in keyof T]: T[K] extends Function ? T[K] | undefined : T[K];
};

// Extract only string properties
type StringProps<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

type User = { id: number; name: string; role: string; active: boolean };
type StringUserProps = StringProps<User>;
// { name: string; role: string }
Enter fullscreen mode Exit fullscreen mode

Key Remapping

// Add 'get' prefix to all keys
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<{ name: string; age: number }>;
// { getName: () => string; getAge: () => number }
Enter fullscreen mode Exit fullscreen mode

API Response Transformer

// Convert snake_case API response to camelCase TypeScript
type CamelCase<S extends string> =
  S extends `${infer H}_${infer T}`
    ? `${H}${Capitalize<CamelCase<T>>}`
    : S;

type CamelCaseKeys<T> = {
  [K in keyof T as CamelCase<string & K>]: T[K];
};

type ApiUser = { user_id: string; first_name: string; created_at: string };
type AppUser = CamelCaseKeys<ApiUser>;
// { userId: string; firstName: string; createdAt: string }
Enter fullscreen mode Exit fullscreen mode

Advanced TypeScript patterns — mapped types, conditional types, template literals — are used throughout the AI SaaS Starter Kit codebase for fully type-safe APIs.

Top comments (0)