Reputation: 313
I want to create a Type in typescript which can convert a string to uppercase:
type _Upper<T> = ...
_Upper<'abc'> // 'ABC'
can anyone tell me this?
Upvotes: 4
Views: 5224
Reputation: 328142
The correct way to do this is to just use the existing Uppercase<T>
utility type. This is an intrinsic
type as introduced in microsoft/TypeScript#40580, which means that it is implemented in the compiler itself; you can look at checker.ts
at approximately line 15,085:
case IntrinsicTypeKind.Uppercase: return str.toUpperCase()
That means you could define your custom _Upper<T>
type like this:
type _Upper<T extends string> = Uppercase<T>;
Yes, that's a trivial definition. But it has the advantage of being simple to define and working according to the Unicode standard case mapping algorithm:
type ABC = _Upper<'abc'>
// type ABC = "ABC"
type Sentence = _Upper<"The quick brown fox jumps over the lazy dog.">;
// type Sentence = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG."
type Хорошо = Uppercase<"я не знаю">;
// type Хорошо = "Я НЕ ЗНАЮ"
If you don't want to use Uppercase<T>
, you can use recursive template literal types to implement something yourself. But note that to do so accurately requires that you keep a mapping of all Unicode characters which need to be changed when converted to upper case. At the very least that would be a fairly large mapping (over 2,000 characters I think).
If all you care about are the 26 characters of the Latin alphabet, then you can write it like this:
interface Uppers {
a: "A", b: "B", c: "C", d: "D", e: "E", f: "F", g: "G",
h: "H", i: "I", j: "J", k: "K", l: "L", m: "M",
n: "N", o: "O", p: "P", q: "Q", r: "R", s: "S", t: "T",
u: "U", v: "V", w: "W", x: "X", y: "Y", z: "Z"
}
type _Upper<T extends string, A extends string = ""> =
T extends `${infer F}${infer R}` ?
_Upper<R, `${A}${F extends keyof Uppers ? Uppers[F] : F}`> : A;
So Uppers
is the mapping type from lowercase (keys) to uppercase (values). The _Upper<T>
type parses the string T
character-by-character. If the character F
is lowercase (F extends keyof Uppers
) then it converts it to uppercase (Uppers[F]
), otherwise it leaves it alone (F
). It's a tail-recursive definition (using the A
type parameter as an accumulator) so in TypeScript 4.5 and above, this will work for quite long string literals.
Let's test it:
type ABC = _Upper<'abc'>
// type ABC = "ABC"
type Sentence = _Upper<"The quick brown fox jumps over the lazy dog.">;
// type Sentence = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG."
type Плохо = _Upper<"я не знаю">;
// type Плохо = "я не знаю" // oops
Well, the first two are good.
But the last one doesn't work; the lowercase Cyrillic string is unaffected. This could be fixed, if necessary, by augmenting the Uppers
type with the Cyrillic alphabet. The basic algorithm here, parsing the string and looking up each character in a mapping, is probably the best we can do. Well, the best would be to use the built-in Uppercase<T>
. But if Uppercase<T>
didn't exist, then a recursive conditional template literal type like this would be reasonable.
Upvotes: 8