Convert string literal type into number literal type in TypeScript
1type ToNumber<S extends string> = any; // implementation23type 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:
1type Input = '210';23// 1. Split number into digits4type Step1 = ['2', '1', '0'];5// 2.1. Multiply the previous result by 106// 2.2. Add the current digit7// 2.3. Do it recursively while we have digits8type Step2 = (('2' * 10) + '1') * 10 + '0';910type 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:
- Digits => tuples
- Multiplication by 10 => type
Multiply10
that accepts tuples - Addition => type
Add
that accepts number and digit as tuples
So let's update the approach for the challenge:
1type Input = '210';23// 1. Split number into digits4type Step1 = ['2', '1', '0'];5// 2.1. Use Multiply10 to multiply tuple by 106// 2.2. Use Add to add one tuple to another7// 2.3. Do it recursively while we have digits8type Step2 = Add<Multiply10<Add<Multiply10<'2'>, '1'>>, '0'>;9// 3. Transform tuple to number literal type10type Step3 = [0, 0, 0, 0, 0, ... 204 more ..., 0]['length']1112type 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:
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:
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:
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 ...T12];
Let's save current progress in https://tsplay.dev/WPxJeW and add tests.
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:
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:
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:
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};1314type Multiply10<T extends readonly any[]> = [15 ...T,16 ...T,17 ...T,18 ...T,19 ...T,20 ...T,21 ...T,22 ...T,23 ...T,24 ...T25];2627type Add<N1 extends readonly any[], N2 extends readonly any[]> = [...N1, ...N2];2829type 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:
- With
Multiply10
we multiply the current number by 10 - With
Add
with sum the number and the digit - Do it recursively while we have digits
- 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