Infer and String Literals in TypeScript

Photo by orva studio on Unsplash

Infer and String Literals in TypeScript

We made it to day number ten of our TypeScript challenge journey until Christmas!
In today's prompt, we'll use a couple of topics that we have already covered but with a small twist that will show us a different usage of them.

Hint: We'll revisit conditional types, string literals, and infer.
Let's get to it 💪🏻

Day ten - Dec 10th

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

Challenge: Christmas Street Suffix Tester

The challenge

It's a little known fact that Santa's reindeer are orienteering experts. They're very particular, actually.

To do this work well, they need to do some basic validation on the addresses. There were hopes among some reindeer to introduce a validation library this year, but there was simply too much infighting. It's kindof a mess. You see..

  • Comet and Vixen want to use Zod because they heard from a YouTuber they like that it's the best (they haven't actually looked ito anything else).

  • Cupid and Rudolph are simply too used to JSON Schema with AJV. They don't want to learn a new thing. They both had popular webpack plugins in the past that are no longer used by anyone and now they're a little bitter about change (in general).

  • Then you have Prancer. Prancer doesn't see the point in validation. Prance feels that validating inputs pollutes the code with type gymnastics that add ever so little joy to the development experience. Yep. Even the reindeer have one of these on their team in 2023.

  • Meanwhile, Blitzen is pushing hard for typia because it's so fast (naturally).

  • Dancer and Donner don't seem to ever be able to articulate their opinions and usually just follow the rest of the group.

The Type's API

So, for this year.. nothing fancy. We'll have to just write a StreetSuffixTester from scratch.

This type will take two generic arguments. The first is for the street, and the second is for the suffix we're testing against.

If the street ends with the suffix then the type should return true (otherwise, false).

The code to complete

type StreetSuffixTester = unknown;

The tests

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

type test_0_actual = StreetSuffixTester<'Candy Cane Way', 'Way'>;
type test_0_expected = true;
type test_0 = Expect<Equal<test_0_expected, test_0_actual>>;

type test_1_actual = StreetSuffixTester<'Chocalate Drive', 'Drive'>;
type test_1_expected = true;
type test_1 = Expect<Equal<test_1_expected, test_1_actual>>;

type test_2_actual = StreetSuffixTester<'Sugar Lane', 'Drive'>;
type test_2_expected = false;
type test_2 = Expect<Equal<test_2_expected, test_2_actual>>;

The solution

type StreetSuffixTester<Street extends string, Suffix extends string> = 
  Street extends `${infer StName}${Suffix}` ? true : false;
  • Define a Generic type: This generic type takes two types that are stings: <Street extends string, Suffix extends string>

  • Conditional type to return a boolean: Street extends ${infer StName}${Suffix} ? true : false . This statement checks if the Street type ends with the specified Suffix type. If it does, it evaluates to true; otherwise, it evaluates to false.

At this point, we have extensively talked about conditional types and even touched a bit on infer in this TypeScript fundamental series.

The trick part to solve this prompt is ${infer StName}${Suffix}.

Let me try to break down what is happening here:

  • ${infer StName}: The infer here is telling TypeScript, 'Hey, you figure out and remember whatever is at this spot in the string, in this case, I called it StName. It is just a placeholder value.

  • ${Suffix}: This part represents the generic Suffix type that you provided. It's the suffix we want to check if the street ends with.

Putting them together ${infer StName}${Suffix} means we are looking for a string that ends with the specified suffix, and we are telling TypeScript to infer the prefix of that string and represent it as StName.

If your IDE highlights unused code differently, you might have noticed that StName is not used in the type definition. That is okay since we don’t actually care about it, it just helps us validate our condition.

If you don’t have to have that issue you can replace StName for a _.

Hope you enjoyed today's post, thanks for sticking around, these ten days have flown by. One more day of leveling up our TypeScript fundamentals and one day closer to Christmas.

If you like this content consider checking out what I post on Twitter/X 🐦