TypeScript — Deriving a Union Type From an Object

Published: Fri Apr 15 2022

This write up is based on Matt Pocock’s TypeScript tip thread.

Problem

Say that we have some object (with some properties, of course), say:

const fruitCounts = {
  apple: 1,
  pear: 2,
}

And we’d like another type that represents individual fruit counts, i.e we want to end up with the following type:

type SingleFruitCount =
  | {
      apple: number
    }
  | {
      banana: number
    }

// in use
const singleFruitCount: SingleFruitCount = {
  apple: 1,
}

This works, but typing it manually this way is clumsy. We can imagine how hairy the type would get with a large number of properties. Ideally, we’d be able to derive the type from the fruitCounts object itself.

Solution

Here’s the derivation:

// store type so that its keys can be used
type FuritCounts = typeof fruitCounts

// our derivation
type SingleFruitCount = {
  [K in keyof FruitCounts]: {
    [K2 in K]: number
  }
}[keyof FruitCounts]

// in use
const singleFruitCount: SingleFruitCount = {
  apple: 1,
}

Breakdown of the derivation:

We use mapped types to create a type that we can use in the next step:

type SingleFruitCount = {
  // using a mapped type to iterate over fruitCount keys
  [K in keyof FruitCounts]: {
    // using a mapped type (again) over fruitCount keys
    [K2 in K]: number
  }
}

// produces the following type
type SingleFruitCount = {
  apple: {
    apple: number
  }
  pear: {
    pear: number
  }
}

That is not useful in and of itself. To create the desired union out of the type above, we can leverage indexed access types:

type SingleFruitCount = {
	[K in keyof FruitCounts]: {
	  [K2 in K]: number
}[keyof FruitCounts] // form a union and use the properties for index access

// produces the desired union type
type SingleFruitCount= {
    apple: number;
} | {
    pear: number;
}

To further clarify: in the same way that we would retrieve an object property value by passing its key with the square bracket notation (e.g. fruitObject[apple]), we can pick out a type from another type by passing the property key of the sought out type to the original type within square brackets.

We need a union, and the way that keyof works is that for any type T, keyof T produces a union of property names of T. In our case, keyof FruitCounts creates a union of apple | pear since those are the property names of FruitCounts.

Once the property name union has been formed by keyof, the types that are mapped to those property names are returned and we end up with the final result.