Guide on transforming a tuple of random types into a nested type structure with the help of recursive conditional types

When I responded to the query on whether Typescript Interfaces can express co-occurrence constraints for properties, I shared the following code snippet:

type None<T> = {[K in keyof T]?: never}
type EitherOrBoth<T1, T2> = T1 & None<T2> | T2 & None<T1> | T1 & T2

type CombinationOf<T> = T extends [infer U1, infer U2] ? EitherOrBoth<U1, U2> :
                        T extends [infer U1, infer U2, infer U3] ? EitherOrBoth<U1, EitherOrBoth<U2, U3>> :
                        T extends [infer U1, infer U2, infer U3, infer U4] ? EitherOrBoth<U1, EitherOrBoth<U2, EitherOrBoth<U3, U4>>> :
                        never;

type Monolith = CombinationOf<[Data1, Data2, Data3]>

The current implementation of CombinationOf supports tuples with up to four elements. While it can be extended easily, I am contemplating if the introduction of recursive conditional types could allow for a more elegant solution.

Therefore, my inquiries are:

  • Can recursive conditional types be used to map tuples of types (of any length) to nested types?
  • If so, is there a maximum depth limit for recursion? (Ideally, this should only be a theoretical concern)
  • How do I assure myself that a newly generated type using recursive conditional types is equivalent to my existing code?

Playground link

Answer №1

With the introduction of recursive conditional types in TypeScript 4.1, you can now create a type called CombinationOf<T> using this syntax:

type CombinationOf<T> =
  T extends [infer U1, ...infer R] ? (
    R extends [] ? never : EitherOrBoth<U1,
      R extends [infer U2] ? U2 : CombinationOf<R>
    >
  ) : never;

This definition closely resembles your original one, maintaining the structure while disregarding the functionality of the EitherOrBoth<> type. It's worth noting that because your initial definition returns never for single-element tuples, this revised version is slightly more complex. (I would have expected CombinationOf<[X]> to simply be

X</code, but oh well 🤷‍♂️)</p>
<p>If <code>T
has 0 or 1 elements, the result is never. For two elements, it's EitherOrBoth<U1, U2>, where U1 and U2 are the first two elements of T. Else, it's
EitherOrBoth<U1, CombinationOf<R>>
, where R represents the rest of tuple T</code after <code>U1.


There is indeed a limited depth allowed, typically around 10 levels:

type TestDepth = CombinationOf<[1, 2, 3, 4, 5, 6, 7, 8, 9]> // works fine
type TestDepthBad = CombinationOf<[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]> // error, exceeds depth limit

You could potentially refactor CombinationOf to group tuples into sets of 3 or 4, raise the depth limit—albeit with heightened complexity—and possibly achieve an increased limit (say 30 or 40... although playing around hasn't yielded values higher than 25), though it may not be warranted.


To demonstrate equivalence between these types, comparing CombinationOf against yours (CombinationOfOrig) can be done manually or via an inspector function:

type IfEquals<T, U, Y = unknown, N = never> =
  (<G>() => G extends T ? 1 : 2) extends
  (<G>() => G extends U ? 1 : 2) ? Y : N;
type TestEqualCombinationOf<T> = 
  IfEquals<CombinationOfOrig<T>, CombinationOf<T>, "Same", "Oops">;

In this example, IfEquals<T, U, Y, N> evaluates to Y if T and U are identical types, else N. Refer to this GitHub comment for further insights on this equality operator.

TestEqualCombinationOf<T> will output "Same" when CombinationOfOrig<T> matches CombinationOf<T>, otherwise "Oops". Below are some test results:

type TestEmpty = TestEqualCombinationOf<[]> // Same
type TestSingleton = TestEqualCombinationOf<[Data1]> // Same
type TestPair = TestEqualCombinationOf<[Data1, Data2]>; // Same
type TestTriple = TestEqualCombinationOf<[Data1, Data2, Data3]>; // Same
interface Data4 { four: 4 }
type TestQuadruple = TestEqualCombinationOf<[Data1, Data2, Data3, Data4]>; // Same

interface Data5 { five: 5 }
type TestQuintuple = TestEqualCombinationOf<[Data1, Data2, Data3, Data4, Data5]>; // Oops

Hence, for tuples up to length 4, both definitions appear equivalent experimentally. The "Oops" result for TestQuintuple was anticipated, given the original limitation to tuples of length 4.


Link to Play area code

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

In TypeScript, the argument 'xxx' cannot be passed to a parameter expecting a 'string' type

When I try to create a new Error object with the message {code: 404, msg: 'user is not existed'} in TypeScript, I receive the following error message: [ts] Argument of type '{ code: number; msg: string; }' is not assignable to paramete ...

What is the best method for calculating the total sum by multiplying the values in an array?

In my current project, I have an array consisting of multiple objects, each containing a property named "amount". My goal is to sum up all these amount values to get the total. Initially, I attempted to use a for loop but encountered an issue where settin ...

the Sprite fails to appear on the screen

Can you help me figure out how to fix the issue I'm having with loading images in this component? Currently, when I refresh the page, the image does not load properly and appears resized to 1 pixel width. Should I wait for the image to fully load befo ...

Tips for showing a DialogBox when a blur event occurs and avoiding the re-firing of onBlur when using the DialogBox

Using React and Material UI: In the code snippet provided below, there is a table with TextFields in one of its columns. When a TextField triggers an onBlur/focusOut event, it calls the validateItem() method that sends a server request to validate the ite ...

Guide to automatically blur the input field and toggle it upon clicking the checkbox using Angular

<input (click)="check()" class="checkbox" type="checkbox"> <input [(ngModel)]="second_month" value="2019-09" type="month" [class.blurred]="isBlurred">> I need the second input(type=month) field to be initially blurred, and only unblur ...

Creating a generic component map resolver for flexible applications

Currently, I am engaged in a project where the backend allows for the modeling of dynamic content that is later displayed as Components on the frontend. Everything seems to be functioning well, except when dealing with models where the dynamic content con ...

What is the method for referencing a subtype within an established type?

When working with React-native, I came across a component called FlatList which includes a property known as ListHeaderComponent. My question is how to specify the type of this property without having to manually copy and paste the original type. Currentl ...

Tips on sorting through an array using Angular 11 and data

I'm working on a subpage that contains a large amount of detailed data in the form of thousands of records. I want to filter this data based on the "id" from my route, which is also included in the dataset. However, I've run into some difficultie ...

The use of async/await within an observable function

I am looking to establish an observable that can send values to my observers. The issue lies in the fact that these values are acquired through a method that returns a promise. Is it possible to use await within the observable for the promise-returning f ...

The essential guide to creating a top-notch design system with Material UI

Our company is currently focusing on developing our design system as a package that can be easily installed in multiple projects. While the process of building the package is successful, we are facing an issue once it is installed and something is imported ...

Solution: How to fix the error: Invalid component type, 'Draggable' cannot be used with JSX in react-draggable

I encountered an error while working on this Next.js React project Type error: 'Draggable' cannot be used as a JSX component. Its instance type 'Draggable' is not a valid JSX element. The types returned by 'render()&apo ...

Issues are arising with Angular Form where the FormControl is not being properly set up for the first field in my form

After grappling with this issue for several weeks, I am still unable to pinpoint the cause. (Angular version: 16.1.4) The form component is populated using a BehaviorSubject, and although the console prints out the correct values for both the form and dat ...

Creating a unique Elastic IP address for a single EC2 instance with the AWS CDK

I'm having an issue with my AWS CDK Stack where multiple Elastic IPs are being created for each public subnet in my VPC instead of just one. I only want one Elastic IP to be associated with a single EC2 instance. My simplified code snippet is as foll ...

Leveraging WebStorm's TypeScript support in conjunction with node_modules

Attempting to set up a TypeScript project in WebStorm to import a Node.js module has been a struggle. I made sure to download the necessary TypeScript definition in settings and specified --module commonjs in the compiler settings. However, I keep running ...

Is it possible to validate a template-driven form without using the model-driven approach?

Attempting to validate a template-driven form in Angular without two-way data binding has proved to be challenging. I have successfully implemented validation using [(ngModel)], but running into an error when trying to validate the form without the MODEL p ...

Verify whether the object is properly implementing the interface

Design: export interface Person { id: number; firstName: string; lastName: string; age: number; } Is there a way to verify that an object returned from the backend aligns with the structure defined in the Person interface? ...

how to sort arrays in javascript

When it comes to filtering items based on specific criteria like fruit and vegetable filters, what is the most effective method to retrieve items that meet both filter requirements? fruitfilter: [] = [{fruitname: "apple"} , {fruitname: "orange"}] vegeta ...

The issue with Angular2 Material select dropdown is that it remains open even after being toggled

Exploring the world of Node.js, I am delving into utilizing the dropdown feature from Angular Material. However, an issue arises once the dropdown is opened - it cannot be closed by simply clicking another region of the page. Additionally, the dropdown lis ...

Implementing generics in TypeScript for objects made easy with this guide!

My question is regarding a function that utilizes generics and selects data from an object based on a key. Can we use generics inside the type of this object, or do we have to create a separate function for options? enum Types { book = 'book', ...

Mongoose: An unexpected error has occurred

Recently, I developed an express app with a nested app called users using Typescript. The structure of my app.js file is as follows: ///<reference path='d.ts/DefinitelyTyped/node/node.d.ts' /> ///<reference path='d.ts/DefinitelyTyp ...