Key Remapping with as

Renaming Keys

TypeScript 4.1 added as in mapped types to rename or filter keys. Combine it with template literal types and Capitalize. Mapping a key to never removes it.

Example

type Product = { id: number; name: string; price: number; internalNotes: string };

type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
type WithoutInternal<T> = { [K in keyof T as K extends `internal${string}` ? never : K]: T[K] };
type OnlyNumbers<T> = { [K in keyof T as T[K] extends number ? K : never]: T[K] };

const p: Product = { id: 1, name: "Kettle", price: 1299, internalNotes: "supplier margin 12%" };

const getters: Getters<Product> = {
  getId: () => p.id,
  getName: () => p.name,
  getPrice: () => p.price,
  getInternalNotes: () => p.internalNotes,
};

const publicView: WithoutInternal<Product> = { id: p.id, name: p.name, price: p.price };
const numbers: OnlyNumbers<Product> = { id: 1, price: 1299 };

console.log(getters.getName(), publicView, numbers);

Output

Kettle { id: 1, name: 'Kettle', price: 1299 } { id: 1, price: 1299 }

Wrong Remapped Key

Example

The generated getter is getName, not getname (TS2561).

type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K] };
const g: Getters<{ name: string }> = { getname: () => "x" };

Compiler output (tsc)

Line 2: error TS2561: Object literal may only specify known properties, but 'getname' does not exist in type 'Getters<{ name: string; }>'. Did you mean to write 'getName'?