A mapped type creates an object type by selecting property keys and assigning a type to each key. It can transform an existing type or create one from a union of keys. This is useful when several object types need the same change without listing every property again. section 13.5 section 13.5.6
Start with an unchanged mapping
If Product has name: string and price: number, this mapping produces an equivalent type: type MappedProduct = { [P in keyof Product]: Product[P] }; keyof Product supplies the keys. The in clause visits each one, and Product[P] looks up its original type. Replacing Product with a generic parameter makes the same mapping reusable: type Mapped<T> = { [P in keyof T]: T[P] }; section 13.5 section 13.5.2
Change the part of the mapping that matches the result you need:
- Change property types with [P in keyof Product]: Product[P] | string. This lets price accept either a number or a string. section 13.5.1
- Rename keys with [P in keyof Product as `${P}Property`]: Product[P]. The result has nameProperty and priceProperty. section 13.5.1
- Add ? after the key selector to make properties optional, or -? to make them required. Put readonly or -readonly before the selector to add or remove read-only status. section 13.5.3
Use built-in mappings when they already express the change: Partial<T>, Required<T>, and Readonly<T> adjust modifiers; Pick<T, K> keeps selected keys, while Omit<T, K> excludes them. There is no built-in mapping here for removing readonly. Transformations can also be chained: Readonly<Partial<Pick<T, K>>> selects properties, makes them optional, then makes them read-only. section 13.5.4 section 13.5.5
Create a type from keys
A mapping need not start with an object type: type CustomMapped<K extends keyof any, T> = { [P in K]: T }; CustomMapped<"name" | "city", string> requires two string properties. Record<"name" | "city", string> provides the same result without a custom mapping. section 13.5.6
Change only properties of a chosen type
A conditional type can choose the new type separately for each property: type ChangeProps<T, U, V> = { [P in keyof T]: T[P] extends U ? V : T[P] }; ChangeProps<Product, number, string> keeps name as a string and changes price from number to string. A function returning that shape must supply a matching value, such as a formatted price string. section 13.6.4
Listing 13.28 Identifying properties in the index.ts file in the src folder
import { City, Person, Product, Employee} from "./dataTypes.js";
type unionOfTypeNames<T, U> = {
[P in keyof T] : T[P] extends U ? P : never;
};
type propertiesOfType<T, U> = unionOfTypeNames<T, U>[keyof T];
function total<T, P extends propertiesOfType<T, number>>(data: T[],
propName: P): number {
return data.reduce((t, item) => t += Number(item[propName]), 0);
}
let products = [new Product("Kayak", 275),
new Product("Lifejacket", 48.95)];
console.log(`Total: ${total(products, "price")}`);
For a function that totals a numeric property, this listing maps matching property types to their keys and other types to never. Indexed access then produces the permitted key union, restricting the property-name argument to numeric properties. section 13.6.5
