Mapped Types And String Template Literals in TypeScript

ยท

4 min read

I very feeling very good about his learning momentum. This is my first time taking a Christman countdown programming challenge, and I can say it's both challenging and fun trying to learn and apply something new every day.

Let's keep that momentum and let's just dive into today's challenge ๐Ÿ’ช.

Day seven - Dec 7th

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

Challenge: Filtering The Children (part 2)

The challenge

[transcript of a slack conversation at 11:23pm between Santa and Chipper (one of the elves that worked on the filtering code from yesterday)]

[Santa] We've got a big problem. That code that you gave me yesterday doesn't work!

[Chipper] what doesn't work about it?

[Santa] It turns out I need the data formatted in a completely different way! The inputs and outputs all need to be different.

[Chipper] ok, so it sounds like the requirements have changed. did you ask my manager? it's late and I'm relaxing. I'm in the middle of a game of League of Legends.

[Santa] Is that like RuneScape? Well anyway, would you mind helping me out in a pinch? Think of this as paying your dues for a better position later!

[Chipper] ok. I guess.

[Santa] Wonderful! Here are the inputs and outputs! That oughta be plenty for you! Ok, I gotta get some rest for a golf game I'm having tomorrow. Signing off!

Developing From The Tests

As you can see, sometimes leadership like Santa manage to convince themselves they have fantastic product vision, you'll get little more than basic inputs and outputs, and you'll have to figure out the behavior from there. Don't be flustered. Take a look at the tests and try to figure out what the behavior is supposed to be.

Start by identifying the inputs for our AppendGood type. Ask yourself if there should be any generic type constraints on the inputs (there may not need to be, or at least right away).

Then try to set up a scaffold that will at least return the same values for each property. Your next step is to transform the properties somehow..

The code to complete

type AppendGood = unknown;

The tests

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

type WellBehavedList = {
  tom: { address: '1 candy cane lane' };
  timmy: { address: '43 chocolate dr' };
  trash: { address: '637 starlight way' };
  candace: { address: '12 aurora' };
};
type test_wellBehaved_actual = AppendGood<WellBehavedList>;
type test_wellBehaved_expected = {
  good_tom: { address: '1 candy cane lane' };
  good_timmy: { address: '43 chocolate dr' };
  good_trash: { address: '637 starlight way' };
  good_candace: { address: '12 aurora' };
};
type test_wellBehaved = Expect<Equal<test_wellBehaved_expected, test_wellBehaved_actual>>;

The solution

To solve this, we need to create a type AppendGood<T> that transforms the keys of the input object T by prefixing each key with the string "good_". Here's what the solution looks like:

type AppendGood<T> = {
  [K in keyof T as `good_${string & K}`]: T[K];
};

Let's break down the solution:

  • AppendGood<T> is a mapped type that takes an object type T as its generic parameter. We previously learned about mapped types in this post.

  • [K in keyof T as good_${string & K}]: Then we iterate over each key K of the input object T.

  • good_${string & K}: This is a template literal type that creates a new key by prefixing "good_" to the original key K.

  • : T[K]: This part references the original value type associated with the key K. In this case, it will be something like: { address: '1 candy cane lane' }

To refresh our memory a bit about a mapped type, it's a way to create a new type by transforming each property of an existing type. It uses the in keyword to iterate over the keys of the original type, allowing you to apply a specific operation or modification to each key-value pair.
Mapped types are often used for tasks like creating variations of object types, modifying key names, or filtering properties based on certain conditions.

Another key piece that you might be wondering about is in the string template literal: good_${string & K}
The expression string & K is a type intersection. It represents a type that includes all the properties of both string and K.
When used in the context of mapped types, it ensures that the resulting keys are string literals or can be converted to strings. This helps ensure that the template literal type used for key construction is valid. In the given example, it ensures that the original key K can be used as a string in the template literal "good_${string & K}".

Summary

  • We had a refresh about mapped types

  • We learn how to implement string literals

I don't know about you, but I'm steadily gaining confidence in my TypeScript skills. I trust you found this explanation helpful. Looking forward to our next session tomorrow!

And hey, Christmas dinner is just around the corner ๐Ÿ—๐Ÿท

ย