SyntaxStudy
Sign Up
TypeScript Mapped Types and Key Remapping
TypeScript Beginner 1 min read

Mapped Types and Key Remapping

Mapped types allow you to create new object types by transforming each property of an existing type. The syntax { [K in keyof T]: ... } iterates over all keys of T and applies a transformation to each one. You can change the value type, make properties optional or required, or make them readonly. This is the foundation of many of TypeScript's built-in utility types. Key remapping with as, introduced in TypeScript 4.1, lets you transform the key names during a mapped type iteration. Combined with template literal types, this allows you to rename properties, filter them out using never, or generate new property names from existing ones. Key remapping makes mapped types significantly more expressive. Homomorphic mapped types — those that use keyof T in the mapping — preserve the optional and readonly modifiers of the original properties unless you explicitly change them using + or - prefixes. Non-homomorphic mapped types, which do not use keyof, start fresh with no modifiers. Understanding this distinction helps you predict the output type of complex mapped type operations.
Example
// Mapping over an object type
type Mutable<T> = {
    -readonly [K in keyof T]: T[K]; // remove readonly
};

type Required2<T> = {
    [K in keyof T]-?: T[K]; // remove optional (-)
};

// Key remapping with 'as'
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface UserModel { id: number; name: string; email: string }
type UserGetters = Getters<UserModel>;
// { getId: () => number; getName: () => string; getEmail: () => string }

// Filter keys by value type
type PickByValue<T, V> = {
    [K in keyof T as T[K] extends V ? K : never]: T[K];
};

interface Mixed {
    id: number;
    name: string;
    count: number;
    label: string;
}
type OnlyStrings = PickByValue<Mixed, string>;
// { name: string; label: string }

// Deep partial
type DeepPartial<T> = {
    [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

interface Config {
    server: { host: string; port: number };
    database: { url: string; name: string };
    debug: boolean;
}

const partialConfig: DeepPartial<Config> = {
    server: { port: 8080 }, // only overriding port
};