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:
keyof T
: Thekeyof
TypeScript operator returns a union type of all the keys in the typeT
. For example, ifT
is{ john: {...}; jimmy: {...} }
, thenkeyof T
is"john" | "jimmy"
.[K in keyof T]
: This is the mapped type syntax. It iterates over each keyK
in the union type generated bykeyof T
.Address
: For each keyK
inT
, the corresponding property inPresentDeliveryList
is assigned theAddress
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!