All keys from all objects

Distribute over unions to get all object keys

L earn about TypeScript’s distributive abilities by mapping over union types in this example of retrieving all keys from a union of object types.

We can create a union of an objects keys using the keyof keyword in TypeScript. This works nicely with a single object type.

type Person = {
  name: string;
  age: number;
};
 
type PersonKeys = keyof Person;
//   ^? "name" | "age"

Keys of multiple objects

To get the keys from multiple object types we could create a union of keyof’s but this isn’t very scalable - let’s think bigger.

If we were creating a utility type, we’d have no idea about how many object types we’re working with.

type Person = {
  name: string;
  age: number;
};
 
type Alien = {
  numberOfHeads: number;
  planet: string;
};
 
type PersonAndAlienKeys = keyof Person | keyof Alien;
//     ^? "name" | "age" | "numberOfHeads" | "planet"

Distributing over unions

Let’s create a utility type that receives a union of objects and returns a union containing all object keys.

To do this we need to understand how to “loop over” unions in TypeScript.

You might be thinking that we can just use keyof on the type argument to loop over the union of objects provided as a type argument but this will only return us the keys that are present in all of the objects in the type argument.

If we perform a conditional check that always passes on the type argument, it alters the way TypeScript behaves.

By doing this we distribute over every member in the union, allowing us to build up a union of all object keys.

type Person = {
  name: string;
  age: number;
};
 
type Alien = {
  numberOfHeads: number;
  planet: string;
  name: string;
};
 
type AllTheKeys1<T> = keyof T; ❌
type AllTheKeys2<T> = T extends unknown ? keyof T : never; ✅
 
type PersonAndAlienKeys1 = AllTheKeys1<Person | Alien>;
//     ^? "name"
 
type PersonAndAlienKeys2 = AllTheKeys2<Person | Alien>;
//     ^? "name" | "age" | "numberOfHeads" | "planet"

Remember

Adding a truthy conditional check to a type argument will allow you to map over unions and operate on them individually.