How Intrinsic Type Manipulations work in TypeScript

Johnny Simpson - Jun 19 '22 - - Dev Community

Did you know that TypeScript comes with a bunch of built in string manipulator types? That means we can easily transform a string type into uppercase, lowercase, or capitalized versions of itself. When might this be useful? In lots of situations where you have a fixed set of string values, for example whenever you have a set of strings from an object using something like keyof.

To transform a string into a different version by defining a new type along with one of the type manipulators. For example, below we transform myType into the capitalized version:

type myType = "hi there"
type capitalizedMyType = Capitalize<myType>

let myVar:capitalizedMyType = "Hi there";
Enter fullscreen mode Exit fullscreen mode

In the above example, hi there would throw an error - only Hi there will work with a Capitalize type. Similarly, we can uncapitalize a string using Uncapitalize:

type myType = "HI THERE"
type uncapitalizedMyType = Uncapitalize<myType>

let myVar:uncapitalizedMyType = "hI THERE";
Enter fullscreen mode Exit fullscreen mode

Above, only hI THERE will work. Any other case will fail the check.

Uppercase and Lowercase Intrinsic Type Manipulation

Along with Capitalize, we also have Uppercase and Lowercase. They both do exactly what you think they'd do - one changes all characters to uppercase, and the other to lowercase.

type myType = "this long sentence"
type bigMyType = Uppercase<myType>

let myVar:bigMyType = "THIS LONG SENTENCE"
Enter fullscreen mode Exit fullscreen mode

Lowercase intrinsic type manipulation

Above we've created a type which is the uppercase version of myType. Here is the same example, but in the other direction: making the lowercase version:

type myType = "THIS LONG SENTENCE"
type smallMyType = Lowercase<myType>

let myVar:smallMyType = "this long sentence"
Enter fullscreen mode Exit fullscreen mode

Using with template literals

These types become very useful when working with template literals, where we can enforce a string literal to use a lowercase version - for example for an ID. Here is a version where we have a set number of strings, and we only want to use the lowercase version in the ID, as part of the versionId type:

type versions = "DEV" | "PROD" | "STAGING"
type versionPrefix = "gold-" | "beta-" | "alpha-"
type versionId = `${versionPrefix}${Lowercase<versions>}`

let currentVersion:versionId = "gold-dev"
Enter fullscreen mode Exit fullscreen mode

Above, we have two sets of union types, and we combine them into one versionId. Here, we only accept lowercase letters, but version is uppercase. We can transform it with Lowercase, so gold-dev is valid.

Using with keyof objects

In a similar way, we may receive an object from an API or elsewhere, which has odd or uneven casing. To normalise this, we can use intrinsic type manipulations. Below, I am using keyof to get all the keys in myObject, and making them all lowercase too.

type myObject = {
    firstName: string,
    Age: number,
    Lastname: string
}

type keys = Lowercase<keyof myObject>

let getObjectKey:keys = "firstname"
Enter fullscreen mode Exit fullscreen mode

keys only has the following valid values: firstname | age | lastname. In this example, using intrinsic string manipulation like this can be an easy way to create clean types when the data we have at our disposal has issues.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .