DEV Community

Chris Cook
Chris Cook

Posted on • Originally published at zirkelc.dev

Template Literal Types: How To Type Strings

Template literal types are essentially types for strings. By defining the pattern that a string must match, these types provide a way to validate and infer data. They were introduced about three years ago in TypeScript 4.1. Following the original GitHub PR, the following examples show the versatile functionality TypeScript has gained through template literal types.

String Format Validation

TypeScript's template literals facilitate validation of string formats. In this example, an IPv4Address type is defined that uses template literals to enforce a specific string pattern (an IPv4 address).

// IPv4 address format, e.g. 192.168.0.1
type IPv4Address = `${number}.${number}.${number}.${number}`;

//> Error: Type '"19216801"' is not assignable to type '`${number}.${number}.${number}.${number}`' 
const badIpAddress: IPv4Address = '19216801';

//> OK
const goodIpAddress: IPv4Address = '192.168.0.1';
Enter fullscreen mode Exit fullscreen mode

The IPv4Address type uses a template literal to define the specific pattern of an IPv4 address. If a string does not match this format (for example, badIpAddress), TypeScript issues an error.

Extracting Parts From a String

Template literals can be used to extract components of a string, which is a similar function to parsing strings at compile time. The ExtractIpAddress type is intended to extract the four segments of an IPv4 address.

type ExtractIPv4Address<TIpAddress extends string> =
    TIpAddress extends `${infer A}.${infer B}.${infer C}.${infer D}` ? [A, B, C, D] : never;

//> IPv4AddressParts = ["192", "168", "0", "1"]
type IPv4AddressParts = ExtractIPv4Address<'192.168.0.1'>;  
Enter fullscreen mode Exit fullscreen mode

By using the TypeScript infer keyword within the template literal, each segment of the IP address can be extracted individually. The output is an array of strings corresponding to the segments.

Splitting a String By a Delimiter

Finally, we can use recursive template literal types to mimic the functionality of the split function in JavaScript. The Split type recursively splits a string S into segments around a delimiter D.

// Split a string by a delimiter
type Split<S extends string, D extends string> =
    string extends S ? string[] :
    S extends '' ? [] :
    S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S];

//> IPv4AddressParts = ["192", "168", "0", "1"]
type IPv4AddressParts = Split<'192.168.0.1', '.'>

//> IPv6AddressParts = ["2001", "0db8", "85a3", "0000", "0000", "8a2e", "0370", "7344"]
type IPv6AddressParts = Split<'2001:0db8:85a3:0000:0000:8a2e:0370:7344', ':'>;
Enter fullscreen mode Exit fullscreen mode

Original Example from TypeScript Playground

If S can be split into two segments T and U around the delimiter D, the type returns an array with T and the result of Split<U, D>. The recursion continues until S cannot be split further and an array with S as the only element is returned.

Discover More Template Magic

You can find even more great examples in this GitHub repo. It's a goldmine of clever use cases that show the power of template literals in TypeScript.


I hope you found this post helpful. If you have any questions or comments, feel free to leave them below. If you'd like to connect with me, you can find me on LinkedIn or GitHub. Thanks for reading!

Top comments (3)

Collapse
 
zirkelc profile image
Chris Cook

The type S is a string literal, that means it is effectively a static and immutable string like 192.168.0.1. This literal is a sub-type of the general string type and therefore S extends string is true.

The condition string extends S is effectively a guard for when S is not a specific string literal type. If S is the general string type, the conditional type returns string[] right away.

Example:

type LiteralExample = Split<'hello.world', '.'>;
//    ^? type LiteralExample = ["hello", "world"]

type GeneralStringExample = Split<string, '.'>;
//    ^? type GeneralStringExample = string[]

function splitString<S extends string>(value: S): Split<S, '.'> {
  return value.split('.') as Split<S, '.'>; 
}

const a = splitString('hello.world');
//    ^? type of `a` is ["hello", "world"]

const b = splitString(String(Math.random()));
//    ^? type of `b` is string[]
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ares_4ebaaee3d2ee7fdb79f8 profile image
Ares

What is the difference between string extends S and S extends string?

Collapse
 
niklampe profile image
Nik

Another great post! Thank you