5 ways to use `keyof` operator in TypeScript

Apr 06, 2023#typescript#how-to

The keyof operator in TypeScript is used to obtain the union of keys (property names) from a given object type. It allows you to define a type that represents the keys of an object without having to manually list them out.

By using keyof, you can define types that depend on the properties of an object without actually knowing the specific property names in advance. This allows you to write more generic code that can work with a wide range of objects and properties, making your code more reusable and adaptable.

interface Person {
  name: string;
  age: number;
  address: string;
}

type PersonKey = keyof Person;

function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const person: Person = {
  name: "John",
  age: 30,
  address: "123 Main St",
};

// Once you have a keyof type, you can use it to access the properties 
// of an object in a type-safe way.
const age = getProperty(person, "age");
const address = getProperty(person, "address");

// error: Argument of type '"invalid"' is not assignable to parameter 
// of type '"name" | "age" | "address"'.
const invalid = getProperty(person, "invalid"); 

There are several ways to use the keyof operator in TypeScript, depending on the context and the problem youโ€™re trying to solve. Here are some examples:

  1. You can use keyof to access the property values of an object by using the bracket notation. This allows you to ensure that youโ€™re only accessing valid properties of the object.
interface Person {
  name: string;
  age: number;
}

function printPersonProperty(person: Person, property: keyof Person) {
  console.log(`The person's ${property} is ${person[property]}`);
}

let person = { name: "Alice", age: 25 };

printPersonProperty(person, "name"); // The person's name is Alice
printPersonProperty(person, "age"); // The person's age is 25
  1. You can also use keyof to create generic constraints, which are types that restrict the possible types that a generic parameter can take.
// A function that takes an object and a key of that object, 
// and returns the value of that key
function getPropertyValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

let person = {
  name: "Alice",
  age: 25,
  occupation: "programmer",
};

// Using the function with valid arguments
let sname = getPropertyValue(person, "name"); // name is string
let age = getPropertyValue(person, "age"); // age is number
let occupation = getPropertyValue(person, "occupation"); // occupation is string

// Error: Type '"hobby"' is not assignable to type '"name" | "age" | "occupation"'
let hobby = getPropertyValue(person, "hobby"); 
  1. You can use keyof with number index signature. A number index signature is a way to define an object type that can have any number of properties with numeric keys and a specific value type.
type Attributes = {
  [key: string]: number;
};

function getAttributeValue<T extends Attributes>(obj: T, key: keyof T) {
  return obj[key]; // number
}

let attributes = {
  foo: 1,
  bar: 2,
  baz: 3,
};

getAttributeValue(attributes, "foo"); // 1
getAttributeValue(attributes, 0); // error, 0 is not a key of attributes
  1. The keyof typeof combination, which can be used to get the property names of an object as a union of literal types.
enum ColorsEnum {
  white = "#ffffff",
  black = "#000000",
}

type Colors = keyof typeof ColorsEnum; // "white" | "black"
  1. You can use keyof to define mapped types, which are types that transform one object type into another object type by mapping over its properties. For example, you can use keyof to define a type that maps all properties of an object to optional properties:
interface Person {
  name: string;
  age: number;
  address: string;
}

type Optional<T> = {
  [K in keyof T]?: T[K];
}

type OptionalPerson = Optional<Person>; // equivalent to { name?: string; age?: number; address?: