Template Literal Types

How to use Templace Literal Types to increase safety and reduce work.

  • Typescript
  • Type Safety

Michel Heinser

Software Engineer


The Problem

We have some function the converts a camelCased string, let's say "teamOneDevelopers" into a kebab-cased one: "team-one-developers" and we want a type to represent the output based on the input string. So our outcome should look something like this:

function camelCaseToKebabCase<T extends string>(input: T): CamelCaseToKebabCase<T> {
    ...
}

A Solution

In order to convert a camelCased string to a kebab-cased one we need to find all uppercased letters and replace them with a minus followed by the lowercased version of the letter. By infering parts of the string like below, we can get the type of the first letter of a string, followed by the rest. This should always be possible, unless we have and empty string, so we can just return string in that case.

type CamelCaseToKebabCase<V> =  V extends `${infer A}${infer Rest}`
    ?
    : string

In the case that we do have a letter we need to ckeck if it is uppercase. If it is not a uppercased letter, we can leave it as it is and run the rest of the string through our type again.

type CamelCaseToKebabCase<V> =  V extends `${infer A}${infer Rest}`
    ? A extends Uppercase<A>
        ?
        : `${A}${CamelCaseToKebabCase<Rest>}`
    : string

In the case that we hit an uppercased letter we need to replace it by a minus sign and the lowercased version of it. Afterwards, we still need to run the rest of the string through our type again.

type CamelCaseToKebabCase<V> =  V extends `${infer A}${infer Rest}`
    ? A extends Uppercase<A>
        ? `-${Lowercase<A>}${CamelCaseToKebabCase<Rest>}`
        : `${A}${CamelCaseToKebabCase<Rest>}`
    : string

We are almost done, we just need to fix one minor mistake. When this recursive type reaches the last letter of the string, it calls itself with the rest again. This rest is now just an empty string. In that case we want to return an emtpy string again. This now solves our problem.

type CamelCaseToKebabCase<V> =  V extends ''
    ? ''
    : V extends `${infer A}${infer Rest}`
        ? A extends Uppercase<A>
            ? `-${Lowercase<A>}${CamelCaseToKebabCase<Rest>}`
            : `${A}${CamelCaseToKebabCase<Rest>}`
        : string

Über den Autor

Michel Heinser

Software Engineer