Improved with TypeScript 4.1: Fixed-Size String Literal Type

The latest updates from the TypeScript team have shown significant improvements in string literal typing (4.1 & 4.2). I'm curious if there's a way to define a fixed length string.

For example:

type LambdaServicePrefix = 'my-application-service';
type LambdaFunctionIdentifier = 'dark-matter-upgrader';
type LambdaFunctionName = `${LambdaServicePrefix}-${LambdaFunctionIdentifier}`; // error: longer than 32 characters...

I envision it working something like this, Array<64, string>;. Since TypeScript has Tuple types for arrays with fixed lengths, I could potentially specify an array like this

[string, ... string * 62, string]
.

type FutureLambdaIdType = `${LambdaServicePrefix}-${string[32]}`;

Answer №1

UPDATED with enhanced support for recursive conditional type

In TypeScript, there are currently no built-in regular-expression-validated string types available up to version 4.7. While Template literal types cover some scenarios, they do not cater to all requirements for regex types. If you encounter a situation where template literal types fall short, consider providing feedback on your use case at microsoft/TypeScript#41160. Expressing the concept of a "string limited to a maximum length of N characters," where N extends number, would be straightforward with regex types but challenging with template literals.

Despite these limitations, let's explore how close we can approximate this functionality within TypeScript.


A significant challenge arises in representing the set of all strings shorter than N characters as a specific type such as StringsOfLengthUpTo<N>. Essentially, each instance of StringsOfLengthUpTo<N> is a large union, but due to compiler constraints on unions exceeding ~10,000 members, practical representation is only feasible for strings up to a few characters. For example, supporting the 95 printable ASCII characters limits us to describing StringsOfLengthUpTo<0>, StringsOfLengthUpTo<1>, and possibly StringsOfLengthUpTo<2>, while beyond that capacity becomes an issue due to overwhelming union sizes, like over 800,000 members in the case of StringsOfLengthUpTo<3>. This limitation forces us to forego specific type definitions.


Alternatively, we can view our requirement as a constraint utilized with generics. Introducing a type similar to TruncateTo<T, N>, which accepts a T extends string type and an N extends number, truncates T down to N characters. By enforcing T extends TruncateTo<T, N>, the compiler provides warnings when dealing with overly lengthy strings.

Prior to TypeScript 4.5, restricted recursion capabilities limited creating TruncateTo<T, N> for values of N larger than approximately 20. However, TypeScript 4.5 introduced support for tail recursion elimination on conditional types, enabling the development of TruncateTo<T, N> by incorporating supplemental accumulator arguments:

type TruncateTo<T extends string, N extends number,
    L extends any[] = [], A extends string = ""> =
    N extends L['length'] ? A :
    T extends `${infer F}${infer R}` ? (
        TruncateTo<R, N, [0, ...L], `${A}${F}`>
    ) :
    A

This adaptation employs an A accumulator to construct the target string alongside an L array-like accumulator monitoring the growing length of A, overcoming the absence of a strongly typed length property in string literal types (see ms/TS#34692 for context). The strategy involves progressing character by character until exhausting the original string or reaching the specified length of N. Let's observe its application:

type Fifteen = TruncateTo<"12345678901234567890", 15>;
// type Fifteen = "123456789012345"

type TwentyFive = TruncateTo<"123456789012345678901234567", 25>;
// type TwentyFive = "1234567890123456789012345"

Directly implementing T extends TruncateTo<T, N> raises a circular constraint error. However, devising a helper function like below facilitates bypassing this challenge:

const atMostN = <T extends string, N extends number>(
    num: N, str: T extends TruncateTo<T, N> ? T : TruncateTo<T, N>
) => str;

Hence, invoking

atMostN(32, "someStringLiteral")
prompts either success or warning based on the input string's length. Note the peculiar conditional type assignment of str, aimed at circumventing the circular constraint. Inferring
T</code from <code>str</code subsequently subjected to scrutiny against <code>TruncateTo<T, N>
ensues potential errors. Here's how it functions:

const okay = atMostN(32, "ThisStringIs28CharactersLong"); // successful match
type Okay = typeof okay; // "ThisStringIs28CharactersLong"

const bad = atMostN(32, "ThisStringHasALengthOf34Characters"); // error!
// -------------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// '"ThisStringHasALengthOf34Characters"' is not assignable to parameter of type 
// '"ThisStringHasALengthOf34Characte"'.
type Bad = typeof bad; // "ThisStringHasALengthOf34Characte"

Is it worth the effort? Perhaps. Previous approaches resorted to non-ideal solutions even for fixed-length validations. Despite improvements in the current methodology, attaining a compile-time validation remains fairly cumbersome. Thus, there might still be instances warranting the adoption of regex-supported string types.

Access the code on the TypeScript playground

Answer №2

Typescript currently does not have a built-in way to handle fixed-length strings. Although there is an ongoing proposal with high support, this feature has yet to be implemented.

If you need to work with short fixed-length strings, you can use workarounds like the ones below:

type Char = 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'
type String3 = `${Char}${Char}${Char}`
const a: String3 = 'aa'    // error
const b: String3 = 'bbbbb' // error
const c: String3 = 'ccc'   // OK
const d: String3 = 'abc'   // OK

However, it's important to note that dealing with longer fixed-length strings may lead to errors such as the "Expression produces a union type that is too complex to represent."

Answer №3

type IsThirteen<T extends number> = 13 extends T ? true : never
type IsFifteen<T extends number> = 15 extends T ? true : never

type CountCharacters<S extends string, T extends string[] = []> = S extends `${string}${infer R}`
  ? CountCharacters<R, [...T, string]>
  : T['length'];

type IsLengthThirteenOrFifteen<T extends string> = true extends IsThirteen<CountCharacters<T>>
    ? T
    : true extends IsFifteen<CountCharacters<T>>
        ? T
        : never

function IsLengthThirteenOrFifteenValidator <T extends string>(a: IsLengthThirteenOrFifteen<T>) {
  return a;
}

const result = IsLengthThirteenOrFifteenValidator('1131111111111')

Sources:

Answer №4

There is no direct way to restrict the length of a string using typing or typescript utilities.

Nevertheless, you have the option to employ regex for string validation (which also considers length):

/^([a-zA-Z0-9_-]){1,64}$/

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The module 'angular/common' was not found in the Angular 2 TypeScript

While experimenting with a sample login form in Angular 2, I encountered an issue when trying to import 'Form_Directives' as: import { FORM_DIRECTIVES } from '@angular/common'; An error was displayed stating that the angular/common m ...

The post request is successful in Postman and cURL, however, it faces issues when executed in Angular

A remote server and a local client are set up to communicate through a simple post request. The client sends the request with one header Content-Type: application/json and includes the body '{"text": "hello"}'. Below is the s ...

Removing scrollbar from table in React using Material UI

I successfully created a basic table using react and material UI by following the instructions found at: https://material-ui.com/components/tables/#table. The table is functioning properly, but I am finding the scrollbar to be a bit inconvenient. https:// ...

Determine the accurate data type while iterating through a for loop

I am facing an issue where I have around 40 unique actions defined, all with the same parameters except for each being provided with a different schema which is causing the problem type ActionName = 'replaceText' | 'replaceImage'; type ...

The FileSaver application generates a unique file with content that diverges from the original document

I'm attempting to utilize the Blob function to generate a file from information transmitted via a server call, encoded in base64. The initial file is an Excel spreadsheet with a length of 5,507 bytes. After applying base64 encoding (including newlines ...

Together, we have a shared Yarn JS directory for both the client and server components,

The scenario: both the client and server share a folder named shared when we make changes to the shared folder in our development process, we need the corresponding references to update in both the client and server the server seems to w ...

Is Python equipped with a function that can provide a numerical representation when given a day of the week as input in string form?

If the function is given the string "Monday", it will return 0. If given the string "Tuesday", it returns 1, and this pattern continues with each day of the week... ...

Please ensure that the table contains all the records corresponding to the respective days

I am struggling with figuring out how to display a record of classes in my table view. The UX prototype I need to follow is shown https://i.stack.imgur.com/CISYn.png (the days of the week are in Portuguese: horario = time, segunda = Monday, terça = Tuesda ...

managing commitments in TypeScript

Is there a way to convert a promise into a string, or is there another method for handling this result? I am encountering an error stating "You cannot use an argument of type 'Promise' for a parameter of type 'string'." const pokemonIma ...

Is there a need to remove whitespace for additional characters?

Is there a function available that can trim specified characters or strings? For example: var x = '@@@hello world@@'; console.log(x.trim('@')); // outputs 'hello world' var y = 'hellohellohelloworld'; console.log(y ...

The element 'x' is implicitly bound with a type of 'any'

I've been exploring the world of Nextjs and TypeScript in an attempt to create a Navbar based on a tutorial I found (). Although I've managed to get the menu items working locally and have implemented the underline animation that follows the mou ...

Exploring Parquet Files with Node.js

Looking for a solution to read parquet files using NodeJS. Anyone have any suggestions? I attempted to use node-parquet but found it difficult to install and it struggled with reading numerical data types. I also explored parquetjs, however, it can only ...

When null is assigned to a type in Typescript, it does not result in an error being triggered

Could someone enlighten me on why this code is not causing an error? import { Injectable } from '@angular/core'; interface Animal{ name: string; } @Injectable() export class AnimalService { lion: Animal = null; constructor() {} get(){ ...

Creating XML templates in Angular 7: A comprehensive guide

How do I pass XML values in Angular 7 when the API requires this specific format of XML code? -modifydata "<datasets><dataset select=\""always\""> <replace match=\""Letter/@FName\"" value=\""Nazeeeeeeeeeeeeer\" ...

Is it feasible to alter the file name while utilizing express-fileUpload library?

Is there a way to modify the file name of an uploaded file on the server side? app.post(URL, (req, res) => { let fileName = req.files.file.name; req.fileUpload; res.statusCode = HTTP_OK; res.send("Good Job") }) The settings I have in uploadF ...

How to retrieve the HTTPClient value in Angular?

APIservice.ts public fetchData(owner: any) { return this.http.get(`${this.url}/${owner}`, this.httpOptions).pipe( catchError(e => { throw new Error(e); }) ); } public fetchDataById(id: number, byId:string, owner: any) { ...

Transform a string into a customized date structure

I have a string of 14 digits that looks like this: 20161909132409. I need to convert it into the format YYYY-dd-mm hh:mm:ss, so that it appears as follows: 2016-19-09 13:24:09. Unfortunately, I am not sure how to accomplish this. Can anyone provide me with ...

Creating an enumeration within a class

I'm encountering difficulties when trying to declare an enum element within a class. Despite attempting various methods to declare the enum, I am unable to make it function properly. Here is the (non-functional) class: export class Device extends El ...

Navigating the use of a getter property key within a generic method signature

What I want to do is create a class with descendants that have a method signature that can adapt based on a compile-time fixed property, which can also be overridden. Here's an example: class Parent { public get config() { return { foo: & ...

Bringing in a module that enhances a class

While scouring for a method to rotate markers using leaflet.js, I stumbled upon the module leaflet-rotatedmarker. After installing it via npm, I find myself at a loss on how to actually implement it. According to the readme, it simply extends the existing ...