The indexed access operator obtains the type of one or more properties from another type. Write a type followed by square brackets and a key type: Product["price"] is number. These lookup types are especially useful when generic code must preserve the relationship between an object and one of its properties. section 13.4.3
Look up one property or every property
For a Product with name: string and price: number, Product["price"] produces number. keyof Product produces the key union "name" | "price", so Product[keyof Product] produces string | number. section 13.4.3
Listing 13.11 Using the indexed access operator in the index.ts file in the src folder
import { City, Person, Product, Employee } from "./dataTypes.js";
function getValue<T, K extends keyof T>(item: T, keyname: K) {
console.log(`Value: ${item[keyname]}`);
}
type priceType = Product["price"];
type allTypes = Product[keyof Product];
let p = new Product("Running Shoes", 100);
getValue<Product, "name">(p, "name");
getValue(p, "price");
let e = new Employee("Bob Smith", "Sales");
getValue(e, "name");
getValue(e, "role");
This listing combines direct lookup types with a generic getValue function. K extends keyof T restricts the key, while item[keyname] accesses the corresponding property. section 13.4.3
The return type T[K] keeps a generic function precise. Calling getValue with a Product and "name" yields string; calling it with "price" yields number. The same pattern works with an Employee and its name or role properties. section 13.4.3
Listing 13.12 Using the indexed access operator in the index.ts file in the src folder
import { City, Person, Product, Employee } from "./dataTypes.js";
function getValue<T, K extends keyof T>(item: T, keyname: K): T[K] {
return item[keyname];
}
let p = new Product("Running Shoes", 100);
console.log(getValue<Product, "name">(p, "name"));
console.log(getValue(p, "price"));
let e = new Employee("Bob Smith", "Sales");
console.log(getValue(e, "name"));
console.log(getValue(e, "role"));
This complete getValue implementation returns item[keyname] as T[K], allowing the compiler to determine each result from the supplied object and key. section 13.4.3
Indexed access also supplies property types inside mapped types. In [P in keyof Product]: Product[P], P enumerates each property name and Product[P] retrieves its corresponding type, producing a type equivalent to { name: string; price: number }. section 13.5
Listing 13.15 Using a mapped type in the index.ts file in the src folder
import { City, Person, Product, Employee } from "./dataTypes.js";
type MappedProduct = {
[P in keyof Product] : Product[P]
};
let p: MappedProduct = { name: "Kayak", price: 275};
console.log(`Mapped type: ${p.name}, ${p.price}`);
This mapped Product example copies the name and price properties and their types, then initializes the resulting type with a Kayak and its price. section 13.5
A conditional mapped type can mark nonmatching properties as never, after which indexed access extracts the remaining property-name union. This enables constraints such as accepting only numeric properties when calculating a total. section 13.6.5
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")}`);
The total function derives numeric property names from T and restricts propName accordingly, allowing products to be totaled through "price" while rejecting unsuitable properties. section 13.6.5
