Tips for creating a recursive string literal type in Typescript

I need to create a type that represents a series of numbers separated by ':' within a string. For example: '39:4893:30423', '232', '32:39'

This is what I attempted:

type N = `${number}` | '' 
type NL = `${N}` | `${N}:${NL}` 
// ERROR : Type alias 'NL' circularly references itself.

Why am I unable to achieve this? Is there a different approach that would work?

Answer №1

My understanding of TypeScript is not deep enough to provide a detailed explanation as to why circular references are not allowed in this specific scenario. However, a workaround can be achieved through recursive concatenation of string literal types and leveraging type inference.

As stated in the Typescript documentation:

When used with concrete literal types, a template literal creates a new string literal type by combining their contents.

I speculate that from the perspective of the compiler, template literals may already involve circular references internally, hence making them unattainable for users.

An elementary approach to implementing this type would be to manually define type concatenation for various required lengths. This method is suitable for situations with predefined and limited value sets.

type Primitive = string | number | bigint | boolean | null | undefined

type M1 = number
type Separator = ':'
type Concat<A extends Primitive, B extends Primitive = never> = B extends never
  ? `${A}`
  : `${A}${B}`

type One = Concat<M1>
type Two = Concat<M1, M1>
type Three = Concat<TwoDigits, M1>

type ML =
  | M1
  | One
  | Two
  | Three
  | Concat<Concat<Two, Separator>, TwoDigits>

The above results in the following union type (which could also be directly defined):

type ML =
  | number
  | `${number}${number}`
  | `${number}${number}${number}`
  | `${number}${number}:${number}${number}`

To accommodate an unlimited number of string literals, we utilize inference, array type destructuring, and recursion:

export type Concat<T extends string[]> = T extends [infer F, ...infer R]
  ? F extends string
    ? R extends string[]
      ? `${F}${Concat<R>}`
      : never
    : never
  : ''

Additionally, we introduce a join function and ConcatS type with separators borrowed from this referenced article.

export type Prepend<T extends string, S extends string> = T extends ''
  ? T
  : `${S}${T}`

export type ConcatS<T extends string[], S extends string> = T extends [
  infer F extends string,
  ...infer R extends string[],
]
  ? `${F}${Prepend<ConcatS<R, S>, S>}`
  : ''

function joinWith<S extends string>(separator: S) {
  return function <T extends string[]>(...strings: T): ConcatS<T, S> {
    return strings.join(separator) as ConcatS<T, S>
  }
}

// usage 
const result = joinWith(':')('13', '230', '71238')
// const result: "13:230:71238" = "13:230:71238"

If you wish to reverse engineer this process, you can convert the numeric parts into a string[] tuple, cast each element to a string literal, and concatenate them:

const tuple = <T extends string[]>(...args: T) => args

const m1 = tuple('39', '4893', '30423')
const m2 = tuple('232')
const m3 = tuple('32', '39')

type ML = ConcatS<typeof m1, ':'> | ConcatS<typeof m2, ':'> | ConcatS<typeof m3, ':'>

This will yield the following union type (which could alternatively be explicitly declared as follows):

type ML = "39:4893:30423" | "232" | "32:39"

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

Using sl-vue-tree with vue-cli3.1 on internet explorer 11

Hello, I am a Japanese individual and my proficiency in English is lacking, so please bear with me. Currently, I am using vue-cli3.1 and I am looking to incorporate the sl-vue-tree module into my project for compatibility with ie11. The documentation menti ...

Unable to render React component after updating its state

After successfully retrieving data from my API, React is failing to render the cards. export default function Subjects() { const userApi = useUserService(); const auth = useRecoilValue(AuthAtom); const [loading, setLoading] = React.useState<boolea ...

Tips for relocating the indicators of a react-material-ui-carousel

I am working with a carousel and dots indicators, but I want to move the indicators from the bottom to the circular position as shown in the image below. I attempted using a negative margin-top, but the indicators ended up being hidden. Is there another ...

Autocomplete feature shows usernames while storing corresponding user IDs

I am looking to enhance the autocomplete functionality in my application while also ensuring that the selected user ID is stored in the database. Specifically, I want the autocomplete feature to display user names for selection purposes, but instead of re ...

Error message indicating a problem with global typings in Angular 2 when using Webpack is displayed

My project is utilizing angular 2 with webpack and during the setup process, I encountered Duplicate identifier errors when running the webpack watcher: ERROR in [default] /angular/typings/globals/node/index.d.ts:370:8 Duplicate identifier 'unescape& ...

Issue TS2339: The object does not have a property named 'includes'

There seems to be an issue that I am encountering: error TS2339: Property 'includes' does not exist on type '{}'. This error occurs when attempting to verify if a username is available or not. Interestingly, the functionality works ...

Optimizing File Transfers and Streaming Using Next.js and CDN Integration

As I work on developing a download system for large files on my website using Next.js and hosting the files on a CDN, I face the challenge of downloading multiple files from the CDN, creating a zip archive, and sending it to the client. Currently, I have i ...

Can a strict type be created from a partial type?

By utilizing TypeScript 2.1, we have the ability to generate a partial type from a strict type as demonstrated below: type Partial<T> = { [P in keyof T]?: T[P]; }; type Person = { name: string, age: number } type PersonPartial = Partial<Pers ...

Key Assignment in Vue FireStore - Potential Undefined Object Situation

My goal is to assign Firestore data, passed through props, to a reactive proxy object in Vue. However, I am encountering an error that says: Object is possibly 'undefined'. (property) fireStoreData: Record<string, any> | undefined To strea ...

When a user clicks on empty space in Angular 2, the page will automatically redirect

When I receive a response from the server, I want to redirect to another page. However, this process takes around 60 seconds, so in the meantime, I want to display a spinner. Once the response is received, I should be redirected to the new page. Sounds sim ...

An error occurs when attempting to create a document using the context.application.createDocument method in the Word Javascript

The Scenario As I work on developing a Word add-in using the latest Javascript API's for Office, I have incorporated various functionalities along with templates. One of the client's requests is to have the templates accessible from the ribbon. ...

Set certain properties within the nested object to be optional

Within a TypeScript project, there exists a type definition that looks like this: type Bar = { x: string; y: string; data: { z: string; w: string; }; }; This type is imported and widely used throughout the project, making it impossible for ...

Assign a property to an array of objects depending on the presence of a value in a separate array

Looking to manipulate arrays? Here's a task for you: const arrayToCheck = ['a', 'b', 'c', 'd']; We have the main array as follows: const mainArray = [ {name:'alex', code: 'c'}, ...

What is an example of an array attribute within a generic class?

In my typescript code, I have created a generic class with two properties like this - export class WrapperModel<T>{ constructor(private testType: new () => T) { this.getNew(); } getNew(): T { return new this.testType ...

Next.js is experiencing issues with the build process

I encountered an issue while working on a Next.js project with NextAuth.js. The problem arises when I try to define my authOptions, as a TypeScript error indicates that the object is not compatible with the expected type for AuthOptions. Here's the sn ...

Display JSX using the material-ui Button component when it is clicked

When I click on a material-ui button, I'm attempting to render JSX. Despite logging to the console when clicking, none of the JSX is being displayed. interface TileProps { address?: string; } const renderDisplayer = (address: string) => { ...

A tutorial on how to customize the hover effect for TableHead Column Identifiers in MaterialUI by adjusting

I'm struggling to customize the appearance of child th elements using the TableHead component from MaterialUI. While I've been successful in modifying various properties, I'm facing difficulty in changing the hover color. Below is the snipp ...

Are there more efficient methods for utilizing local scope when defining a variable?

Having experience in the ML world, I'm used to creating variables with limited scope like this: let myVar = let result1 = doSomething() let result2 = doSomethingElse() result1 + result2 In TypeScript, it seems you can achieve similar sco ...

The export 'ChartObject' is not available in highcharts

Trying to integrate highcharts into my Angular 4 project has been a bit challenging as I keep encountering the following error: highcharts has no exported member 'ChartObject' I have experimented with different options such as angular-highchart ...

Unable to locate the reference to 'Handlebars' in the code

I am currently attempting to implement handlebars in Typescript, but I encountered an error. /// <reference path="../../../jquery.d.ts" /> /// <reference path="../../../require.d.ts" /> My issue lies in referencing the handlebars definition f ...