fawinell
fawinell

Reputation: 313

typescript: how to create a Type which can convert a string to uppercase

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

Answers (1)

jcalz
jcalz

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.

Playground link to code

Upvotes: 8

Related Questions