Advent of TypeScript 2023 - Part. I
Erhan Tezcan
Posted on December 26, 2023
Advent of TypeScript 2023 is a series of challenges related to type-level TypeScript.
This page provides walkthroughs for days 1 to 10.
You can find the solutions & tests at https://github.com/erhant/aot-2023
Day 1. Christmas Cookies
We simply have to provide a Union type, as follows:
type SantasFavoriteCookies = "ginger-bread" | "chocolate-chip";
Day 2. Cookie Inventory
We are required to return the keys in the given object, thankfully keyof
operator does just that!
type CookieSurveyInput<T> = keyof T;
Day 3. Gift Wrapper
We have 3 parameters, and in the given order they must be assigned as values to keys present
, from
and to
. Well, working with objects in the type-world is very similar to JS, as you can see:
type GiftWrapper<P, F, T> = {
present: P;
from: F;
to: T;
};
Day 4. Delivery Addresses
Here, we need to change the values of a given object. We can access the keys of an object via K in keyof T
; remember keyof T
returns a union and in
tells us that we want all the values in the union. Our solution is therefore:
type Address = { address: string; city: string };
type PresentDeliveryList<T extends Record<PropertyKey, any>> = { [K in keyof T]: Address };
We don't really need
T extends Record<PropertyKey, any>
here for the tests, but that provides a bit more type-safety where unsuitable parameters toPresentDeliveryList
would cause an error.
Day 5. Santa's List
Here we are asked to concatenate two arrays. We must remember that spread operator ...array
works in type-world too! So, if we have two arrays A, B
we can spread them into a single array [...A, ...B]
and the result will be as if we had called A.concat(B)
.
type SantasList<A extends readonly any[], B extends readonly any[]> = [...A, ...B];
Day 6. Filtering the Children I
We are asked to exclude an item from a union. TypeScript has a built-in called Exclude
just for that!
type FilterChildrenBy<T, E> = Exclude<T, E>;
Day 7. Filtering the Children II
We have worked with changing the value of a record before in day 4; now, we are asked to change the keys! This is a bit more work, but we will make use of something called key remapping which allows one to re-map keys in a mapped type.
type GoodProperty<K extends PropertyKey> = K extends string ? `good_${K}` : never;
type AppendGood<T extends Record<string, any>> = { [K in keyof T as GoodProperty<K>]: T[K] };
One thing to notice here:
type AppendGood<T extends Record<string, any>> = { [K in keyof T as `good_${K}`]: T[K] };
The code above will not work because a string literal accepts things that can be stringified, however a property (via
keyof
) can return aSymbol
which wouldn't be compatible with our string literal there.I didn't know about key-remapping until this challenge!
Day 8. Filtering the Children III
In this challenge, we will filter out some of the keys based on how they fit a given string. We can use Exclude
for this as well! If you hover over the built-in Exclude
you will see that it is written as:
type Exclude<T, U> = T extends U ? never : T;
So if a given string T
extends U
, it will return never
. If a mapped-key is never
it is not included in the resulting object. With this in mind, our solution is:
type RemoveNaughtyChildren<T> = { [K in Exclude<keyof T, `naughty_${string}`>]: T[K] };
Notice how we access the respective values of each key via
T[K]
Day 9. Is Santa Dyslexic?
We are asked to reverse a string, at type-level! Although this may sound scary at first, it is not so scary when you have knowledge of the infer
keyword. Inferring is done within a conditional type, you can learn more about it here.
We can ask if a type extends some other type, and infer parts of it based on whether that condition is true or not! In this challenge, we can ask if our type extends a string with two inferred parts: T extends ${infer F}${infer S}
. It just turns out that F
will be the first character here, and S
will be the rest of the string.
So, we can extract F
and S
like that, and put F
at the end of a new string, prefixed by Reverse<S>
yet again to reverse the remaining string. In the end, there will be no characters left, and at that point we will return the string itself.
type Reverse<T extends string> = T extends `${infer F}${infer S}` ? `${Reverse<S>}${F}` : T;
When
T = 'a'
, that condition will result inF = 'a'
andS = ''
.
Day 10. Street Suffix
In this challenge, we just need to make a suffix check, a job suited for the beloved condition-type together with a string literal.
type StreetSuffixTester<T extends string, S extends string> = T extends `${string}${S}` ? true : false;
The code is pretty self-explanatory in this one.
Posted on December 26, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.