Exploring generic types using recursive inference

The scenario:

export type SchemaOne<T> =
  | Entity<T>
  | SchemaObjectOne<T>;
export interface SchemaObjectOne<T> {
  [key: string]: SchemaOne<T>;
}
export type SchemaOf<T> = T extends SchemaOne<infer R> ? R : never;

const sch: Entity<ArticleResource> = ArticleResource.getEntitySchema();
const a = { a: sch  };
const aa: SchemaOne<ArticleResource> = a;
// it's functioning!
type Z = SchemaOf<typeof a>;
// Z is ArticleResource as expected

const b = { a: { b: sch }  };
type ZZ = SchemaOf<typeof b>;
// ZZ is unknown - SADBEAR

I have managed to correctly match my recursive definition (borrowed from https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540). (hence the works after aa definition). However, I am now trying to infer the type without making it more generic (getting bb's type).

For some reason, this only seems to work one level deep. Is this a limitation of TypeScript? Is there a way to utilize recursion with infer to actually determine the generic type?

Answer №1

Understanding how the compiler infers conditional types can be quite complex. It seems that there is a limit to how many type instantiations it will perform before defaulting to unknown or any. If you find that infer R alone doesn't provide the desired result, consider creating your own method to analyze a type and determine the appropriate return value for R.

In this scenario, let's define some basic types to get started on compiling...

// Hypothetical definitions

interface Entity<T> {
  e: T;
}

class ArticleResource {
  static getEntitySchema<T>(): Entity<T> {
    return null!;
  }
  ar = "ArticleResource";
}

One potential implementation of SchemaOf<T> could look like this:

type _SchemaOf<T> = T extends Entity<infer R>
  ? R
  : T extends object ? { [K in keyof T]: _SchemaOf<T[K]> }[keyof T] : never;

(Named _SchemaOf as it will be used to construct the final SchemaOf). This logic checks if T is simply an Entity<R>, returning R. If not, it attempts to recursively iterate through object properties to extract Entity types and merge them into a union.

This function should ideally return what you expect - for example, _SchemaOf<Entity<A>> yields A, and

_SchemaOf<{a: X, b: Y, c: Z}>
results in
_SchemaOf<X> | _SchemaOf<Y> | _SchemaOf<Z>
, capturing all Entity instances within an object.

However, it may yield unexpected outcomes when provided with non-SchemaOne<T> types, like {a: Entity<A>, b: string}, which returns just A. Therefore, a verification step is necessary:

type SchemaOf<T> = T extends SchemaOne<_SchemaOf<T>> ? _SchemaOf<T> : never;

This check ensures that the result from _SchemaOf<T> aligns with

SchemaOne<_SchemaOf<T>>
. If not, the output is set to never.

To test these implementations:

type Z = SchemaOf<typeof a>;
// Expected output: ArticleResource

const b = { a: { b: sch } };
type ZZ = SchemaOf<typeof b>;
// Expected output: ArticleResource

const c = { a: { b: sch, c: "string" } };
type _C = _SchemaOf<typeof c>; // ArticleResource
type C = SchemaOf<typeof c>; // never

The above tests demonstrate the behavior of the implemented functions and ensure that they are functioning as intended. Hopefully, this provides some clarity and helps you progress. Good luck!

Link to 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

Best practices for working with child components in Vue.js using TypeScript: Steer clear of directly mutating props

I feel like I'm stuck in a loop here. Currently, I have a Vue.js 2 app set up and running with TypeScript. However, I'm encountering an issue when trying to pass data to a child component that originates from the store. <template> < ...

Why is my Vue view not being found by Typescript (or possibly Webpack)?

npx webpack TS2307: Cannot locate module './App.vue' or its corresponding type declarations. I am currently utilizing webpack, vue, and typescript. My webpack configuration is pretty basic. It uses a typescript file as the entry point and gener ...

Express displays html instead of json when error handling occurs

I recently followed a tutorial on Express.js to create a simple error handler. function clientErrorHandler(err, req, res, next) { if (req.xhr) { console.log('clienterrorhandler', err); res.status(500).send({ error: 'Something faile ...

Having trouble with the react event handler for the renderedValue component in Material UI?

I am facing an issue while trying to utilize the onDelete event handler within the chip component using Material UI in the code snippet below. Upon clicking on the chip, it triggers the Select behavior which opens a dropdown menu. Is there a way to modif ...

Angular: merging multiple Subscriptions into one

My goal is to fulfill multiple requests and consolidate the outcomes. I maintain a list of outfits which may include IDs of clothing items. Upon loading the page, I aim to retrieve the clothes from a server using these IDs, resulting in an observable for e ...

Using Karma-Jasmine to Import Spy without anyImplicitAny

If I include the configuration setting noImplicitAny in the tsconfig.json file of my Angular 4+ project: "noImplicitAny": true, ...and then try to import and use Spy in a unit test: import { Spy } from "karma-jasmine"; I encounter this console error wh ...

Is there a way to incorporate margins into a React component using TypeScript?

I am currently facing a challenge in passing CSS attributes to a component that I am working with. The reason behind this is that I need to modify the margins to fit a specific space, hence my attempt to directly pass the margins. Does anyone have any sug ...

Executing the routing component prior to any other tasks

Having an issue where the ProductsService is fetching data from the server and storing it in an Array. The ProductsComponent serves as the parent component, while the ProductsListComponent and ProductListItemsComponent are its children components. The flow ...

Snackbar and RTK Query update trigger the error message: "Warning: Cannot update during an existing state transition."

I've built a basic ToDos application that communicates with a NodeJS backend using RTK Query to fetch data, update state, and store cache. Everything is functioning properly as expected with the communication between the frontend and backend. Recently ...

Is it possible to designate a Typescript generic type as a tuple with equal length to that of another tuple?

Imagine having a function that takes in a dataset which is an array of (identically-typed) tuples: type UnknownTuple = any[] function modifyDataStructure<T extends UnknownTuple>(list: T[]) { ... } The goal is to define a second argument with the ...

Error encountered in Next.js: The function 'useLayoutEffect' was not successfully imported from 'react' (imported as 'React')

I've been in the process of migrating an application from CSR (using webpack only) to SSR, and I'm utilizing Next.js for this transition. Following the migration guide provided by Next.js for switching from vite (specifically focusing on parts r ...

Associative TypeScript Arrays

I'm attempting to organize reservations based on business ID in order to achieve a specific end result. Here is the desired output: [ [businessID1] => [Object1,Object2, Object3], [businessID2] => [Object1,Object2], [businessID3] => [Object1,Objec ...

Utilizing Protractor's advanced filtering techniques to pinpoint the desired row

I am trying to filter out the specific row that contains particular text within its cells. This is my existing code: private selectTargetLicense(licenseName: string) { return new Promise((resolve => { element.all(by.tagName('clr-dg-tab ...

Nestjs is throwing an UnhandledPromiseRejectionWarning due to a TypeError saying that the function this.flushLogs is not recognized

Looking to dive into the world of microservices using kafka and nestjs, but encountering an error message like the one below: [Nest] 61226 - 07/18/2021, 12:12:16 PM [NestFactory] Starting Nest application... [Nest] 61226 - 07/18/2021, 12:12:16 PM [ ...

Tips for extracting specific JSON response data from an array in TypeScript

I have an array named ReservationResponse, which represents a successful response retrieved from an API call. The code snippet below demonstrates how it is fetched: const ReservationResponse = await this.service.getReservation(this.username.value); The st ...

What is the correct way to write SVG markup within SVG tags in a React and NextJS environment?

I currently have a Svg component set up like this interface SvgIconProps { children: React.ReactNode; strokeWidth?: number; width?: number; height?: number; className?: string; } export const SvgIcon = ({ children, strokeWidth = 1, width = ...

Inject props into a Component nested within a Higher-Order-Component (HOC)

In attempting to grasp the concept of creating a React Higher Order Component from this particular article, I find myself struggling to fully understand and utilize this HOC. interface PopupOnHoverPropType { hoverDisplay: string; } const WithPopupOnHov ...

Angular is experiencing difficulty locating the routing path for the auxiliary `router-outlet`

Exploring the intricacies of routing in Angular to gain a deeper understanding of the concept. Encountering an issue where I am receiving an exception NG04002: Cannot match any routes. URL Segment: 'about' when attempting to click on the About li ...

Exploring the methods for monitoring multiple UDP ports on a single address in Node.js within a single process

I am currently working on developing a Node.js application to manage a small drone. The SDK provides the following instructions: To establish a connection between the Tello and a PC, Mac, or mobile device, use Wi-Fi. Sending Commands & Receiving Responses ...

Update of Angular Material Table rows triggers a popup, however only the values from the first array are populated in all edited rows

Developed an application with two components (A & B) that includes a popup dialog for editing: Component A fetches the data from a service and loads it into a data table Component B initializes the data when a pop event is triggered from A. Usually, ...