beraliv

Convert string literal type into number literal type in TypeScript

Example of StringToNumber use
1type ToNumber<S extends string> = any; // implementation
2
3type cases = [
4 Expect<Equal<ToNumber<"0">, 0>>,
5 Expect<Equal<ToNumber<"27">, 27>>
6];

Today we discuss StringToNumber

The type StringToNumber is identical to the method Number.parseInt in JavaScript

It's not easy topic but let's try what we can do here 🔥

Idea

Let's quickly plan how we can solve the challenge:

Idea 1
1type Input = '210';
2
3// 1. Split number into digits
4type Step1 = ['2', '1', '0'];
5// 2.1. Multiply the previous result by 10
6// 2.2. Add the current digit
7// 2.3. Do it recursively while we have digits
8type Step2 = (('2' * 10) + '1') * 10 + '0';
9
10type Result = 210;

Looks like a plan, but...

The problem is that we don't have multiplication * and addition + operators in TypeScript (by version 4.5) that can be applied for number literal types.

But instead if we replace:

  1. Digits => tuples
  2. Multiplication by 10 => type Multiply10 that accepts tuples
  3. Addition => type Add that accepts number and digit as tuples

So let's update the approach for the challenge:

Idea 2 of solving the challenge
1type Input = '210';
2
3// 1. Split number into digits
4type Step1 = ['2', '1', '0'];
5// 2.1. Use Multiply10 to multiply tuple by 10
6// 2.2. Use Add to add one tuple to another
7// 2.3. Do it recursively while we have digits
8type Step2 = Add<Multiply10<Add<Multiply10<'2'>, '1'>>, '0'>;
9// 3. Transform tuple to number literal type
10type Step3 = [0, 0, 0, 0, 0, ... 204 more ..., 0]['length']
11
12type Result = 210;

Now it looks clear. Let's try it out!

Convert digits to tuples

As we plan to deal with digits, let's create a mapping between digits and tuples:

Map digits to tuples
1type DigitMapping = {
2 "0": [];
3 "1": [0];
4 "2": [0, 0];
5 "3": [0, 0, 0];
6 "4": [0, 0, 0, 0];
7 "5": [0, 0, 0, 0, 0];
8 "6": [0, 0, 0, 0, 0, 0];
9 "7": [0, 0, 0, 0, 0, 0, 0];
10 "8": [0, 0, 0, 0, 0, 0, 0, 0];
11 "9": [0, 0, 0, 0, 0, 0, 0, 0, 0];
12};

Let's save it in https://tsplay.dev/w6Xqrm and check the length of tuples for all possible digits:

Checking tuples for all digits
1type cases = [
2 Expect<Equal<DigitMapping["0"]["length"], 0>>,
3 Expect<Equal<DigitMapping["1"]["length"], 1>>,
4 Expect<Equal<DigitMapping["2"]["length"], 2>>,
5 Expect<Equal<DigitMapping["3"]["length"], 3>>,
6 Expect<Equal<DigitMapping["4"]["length"], 4>>,
7 Expect<Equal<DigitMapping["5"]["length"], 5>>,
8 Expect<Equal<DigitMapping["6"]["length"], 6>>,
9 Expect<Equal<DigitMapping["7"]["length"], 7>>,
10 Expect<Equal<DigitMapping["8"]["length"], 8>>,
11 Expect<Equal<DigitMapping["9"]["length"], 9>>
12];

It's working as expected 💪

Multiply by 10

Next, we want to "multiply" tuples by 10. It means that we want to return the tuple with the length of original tuple multiplied by 10. For that we use spread 10 times:

Multiply tuples by 10
1type Multiply10<T extends readonly any[]> = [
2 ...T,
3 ...T,
4 ...T,
5 ...T,
6 ...T,
7 ...T,
8 ...T,
9 ...T,
10 ...T,
11 ...T
12];

Let's save current progress in https://tsplay.dev/WPxJeW and add tests.

Test cases for multiplication
1type cases = [
2 Expect<Equal<Multiply10<[]>["length"], 0>>,
3 Expect<Equal<Multiply10<[0]>["length"], 10>>,
4 Expect<Equal<Multiply10<[0, 0]>["length"], 20>>,
5 Expect<Equal<Multiply10<[0, 0, 0]>["length"], 30>>,
6 Expect<Equal<Multiply10<[0, 0, 0, 0]>["length"], 40>>,
7 Expect<Equal<Multiply10<[0, 0, 0, 0, 0]>["length"], 50>>,
8 Expect<Equal<Multiply10<[0, 0, 0, 0, 0, 0]>["length"], 60>>,
9 Expect<Equal<Multiply10<[0, 0, 0, 0, 0, 0, 0]>["length"], 70>>,
10 Expect<Equal<Multiply10<[0, 0, 0, 0, 0, 0, 0, 0]>["length"], 80>>,
11 Expect<Equal<Multiply10<[0, 0, 0, 0, 0, 0, 0, 0, 0]>["length"], 90>>
12];

Given tuples that represent all possible digits, we multiply it by 10 and check that it equals to 0, 10, 20, ..., 90 respectively.

Pretty neat, ha 🤓

Addition

Another most important part is adding one tuple to another. That means we want to return the tuple which length is the sum of two tuples' lengths:

Addition
1type Add<N1 extends readonly any[], N2 extends readonly any[]> = [...N1, ...N2];

Let's save it https://tsplay.dev/w8Kq0W and test it as usual:

Test cases for Add
1type cases = [
2 Expect<Equal<Add<[], [0, 0, 0, 0, 0, 0, 0, 0, 0]>["length"], 9>>,
3 Expect<Equal<Add<[0], [0, 0, 0, 0, 0, 0, 0, 0]>["length"], 9>>,
4 Expect<Equal<Add<[0, 0], [0, 0, 0, 0, 0, 0, 0]>["length"], 9>>,
5 Expect<Equal<Add<[0, 0, 0], [0, 0, 0, 0, 0, 0]>["length"], 9>>,
6 Expect<Equal<Add<[0, 0, 0, 0], [0, 0, 0, 0, 0]>["length"], 9>>,
7 Expect<Equal<Add<[0, 0, 0, 0, 0], [0, 0, 0, 0]>["length"], 9>>,
8 Expect<Equal<Add<[0, 0, 0, 0, 0, 0], [0, 0, 0]>["length"], 9>>,
9 Expect<Equal<Add<[0, 0, 0, 0, 0, 0, 0], [0, 0]>["length"], 9>>,
10 Expect<Equal<Add<[0, 0, 0, 0, 0, 0, 0, 0], [0]>["length"], 9>>,
11 Expect<Equal<Add<[0, 0, 0, 0, 0, 0, 0, 0, 0], []>["length"], 9>>
12];

Now we're ready to combine it all together and come to the implementation of StringToNumber

Solution

So let's combine all we have together and come up with the final solution:

Final solution
1type DigitMapping = {
2 "0": [];
3 "1": [0];
4 "2": [0, 0];
5 "3": [0, 0, 0];
6 "4": [0, 0, 0, 0];
7 "5": [0, 0, 0, 0, 0];
8 "6": [0, 0, 0, 0, 0, 0];
9 "7": [0, 0, 0, 0, 0, 0, 0];
10 "8": [0, 0, 0, 0, 0, 0, 0, 0];
11 "9": [0, 0, 0, 0, 0, 0, 0, 0, 0];
12};
13
14type Multiply10<T extends readonly any[]> = [
15 ...T,
16 ...T,
17 ...T,
18 ...T,
19 ...T,
20 ...T,
21 ...T,
22 ...T,
23 ...T,
24 ...T
25];
26
27type Add<N1 extends readonly any[], N2 extends readonly any[]> = [...N1, ...N2];
28
29type ToNumber<
30 S extends string,
31 T extends readonly any[] = []
32> = S extends `${infer D}${infer Rest}`
33 ? ToNumber<Rest, Add<Multiply10<T>, DigitMapping[D & keyof DigitMapping]>>
34 : T["length"];

So we start iterating over the string with conditional type S extends `${infer D}${infer Rest}`.

Given the digit, we apply the idea that we discussed above:

  1. With Multiply10 we multiply the current number by 10
  2. With Add with sum the number and the digit
  3. Do it recursively while we have digits
  4. At the end we have a result tuple, which we convert to number literal type by using ['length']

We also transform digit to tuple using DigitMapping[D & keyof DigitMapping]. We use & keyof DigitMapping to be sure that we have only digits (because the keys of DigitMapping are only digits).

That's basically it 🎉

To be able to check the whole solution with tests, please have a look at the Playground – https://tsplay.dev/NlxoON

Thank you for your time! 🕛

Have a wonderful weekend ☃️ and see you soon! 👋

typescript

Comments

Alexey Berezin profile image

Written by Alexey Berezin who loves London 🏴󠁧󠁢󠁥󠁮󠁧󠁿, players ⏯ and TypeScript 🦺 Follow me on Twitter