Mapped Types in TypeScript

ยท

3 min read

In today's challenge, we'll leverage some topics covered in our previous post about keyof and generic types, plus combine some features of TypeScript that we haven't talked about yet (hit: we'll learn about mapped types).

Let's keep the holiday learning spirit!

Day four - Dec 4th

I strongly encourage you to try the first challenge on your own before reading the solution here.

Challenge: Christmas Present Delivery Addresses

The challenge

[Bernard] This is bullshit, Kris. I've been leading the Elves for 200+ years. Don't you trust that I know what I'm talking about?? WE NEED TO GROW THE TEAM. We're running a skeleton-crew here.

[Santa] Remember, we're like a family here; we all make sacrifices! We're still a startup!

[Bernard] I swear to you. I think if I hear another hussle-culture quip from you.... I think my little elf head will explode.

[Santa] If you can stick to it now and get us through one more year, there will definitely be rewards down the line.

[Bernard] I don't know why I even bothered asking...

Clearly, Bernard is a bit disgruntled. Can you blame him? Alas, there's still more work to do. Bernard walks away and mutters to himself:

[Bernard] Guess it's time to drag myself through another pointless TypeScript type challenge with no practical application.

Poor Bernard. Let's help him out.

Today's task is to craft a type (PresentDeliveryList) that takes an object type as an input and then replaces the values at each property with an Address. We don't know in advance what properties will be provided, which is why it needs to be a generic type. Otherwise Bernard would probably just copy/pasta it to get through the day.

The code to complete

type Address = { address: string; city: string };
type PresentDeliveryList = unknown;

The tests

import { Expect, Equal } from 'type-testing';

type MixedBehaviorList = {
  john: { behavior: 'good' };
  jimmy: { behavior: 'bad' };
  sara: { behavior: 'good' };
  suzy: { behavior: 'good' };
  chris: { behavior: 'good' };
  penny: { behavior: 'bad' };
};
type test_MixedBehaviorTest_actual = PresentDeliveryList<MixedBehaviorList>;

type test_MixedBehaviorTest_expected = {
  john: Address;
  jimmy: Address;
  sara: Address;
  suzy: Address;
  chris: Address;
  penny: Address;
};
type test_MixedBehaviorTest = Expect<
  Equal<test_MixedBehaviorTest_actual, test_MixedBehaviorTest_expected>
>;

The solution

type PresentDeliveryList<T> = {
  [K in keyof T]: Address;
};

The solution for this is to define a mapped type that transforms each property of the input object T into the Address type.

In this type definition, PresentDeliveryList<T> is a generic type that takes an input type T. The mapped type is represented by [K in keyof T], which means "for each key K in the keys of T."

Here's a step-by-step breakdown:

  1. keyof T: The keyof TypeScript operator returns a union type of all the keys in the type T. For example, if T is { john: {...}; jimmy: {...} }, then keyof T is "john" | "jimmy".

  2. [K in keyof T]: This is the mapped type syntax. It iterates over each key K in the union type generated by keyof T.

  3. Address: For each key K in T, the corresponding property in PresentDeliveryList is assigned the Address type.

So, for the MixedBehaviorList example:

type MixedBehaviorList = {
  john: { behavior: 'good' };
  jimmy: { behavior: 'bad' };
  sara: { behavior: 'good' }
  suzy: { behavior: 'good' };
  chris: { behavior: 'good' };
  penny: { behavior: 'bad' };
};

type PresentDeliveryListForMixedBehavior = PresentDeliveryList<MixedBehaviorList>;

The PresentDeliveryListForMixedBehavior type is essentially:

type PresentDeliveryListForMixedBehavior = {
  john: Address;
  jimmy: Address;
  sara: Address;
  suzy: Address;
  chris: Address;
  penny: Address;
};

It transforms each property in MixedBehaviorList (e.g., { behavior: 'good' }) into the Address type.

I really liked today's TypeScript challenge, I hope you enjoyed the prompt and the breakdown. We are one more day closer to Christmas ๐Ÿฆƒโ„๏ธ.

See you tomorrow in the next one!

ย