Issue with NestedKeyof type arising from circularly referencing objects

Currently, I am in the process of constructing a library and my task involves implementing NestedKeyof.

During my research, I came across the following code snippet:

type NestedKeyOf<T extends object> =  {
  [Key in keyof T & (string | number)]: T[Key] extends object 
? `${Key}` | `${Key}.${NestedKeyOf<T[Key]>}`
: `${Key}`
}[keyof T & (string | number)];

This piece of code performs as expected.

However, I am currently encountering difficulties with circular references objects, which is resulting in the following issue:

Type of property 'self' circularly references itself in mapped type '{ [Key in "self" | "name" | "imageManagerIdentifier" | "x"]: Cirulare[Key] extends object ? `${Key}` | `${Key}.${NestedKeyOf<Cirulare[Key]>}` : `${Key}`; }'.(2615)

Provided below is the complete test code:

class Cirulare {
  name: string;
  self: Cirulare;
  public imageManagerIdentifier = 'ImageHandler';
  x: number;
  constructor() {
    (this.name = 'asd'), 
    (this.self = this);
    this.x = 0;
  }
}


type NestedKeyOf<T extends object> =  {
  [Key in keyof T & (string | number)]: T[Key] extends object 
? `${Key}` | `${Key}.${NestedKeyOf<T[Key]>}`
: `${Key}`
}[keyof T & (string | number)];

const fn<T> = (keys: NestedKeyOf<T>[])=> {

}

fn<Cirulare>(["name",...])

I am looking for a way to either disable this warning/error or find a simple solution to overcome it. Any suggestions?

Answer №1

The definition of NestedKeyOf<T> would result in an infinite union when applied to a recursive data structure, such as

"self" | "self.self" | "self.self.self" | "self.self.self.self" | ...
. TypeScript is unable to handle such infinite unions. There is a proposal at microsoft/TypeScript#44792 suggesting the use of template literal types defined in reference to themselves directly, but as of TypeScript 4.9, this feature is not supported.


To address this issue, one solution is to incorporate a depth limiter type parameter into the type definition. By specifying the depth limit with D, each nested evaluation reduces the limit until reaching zero, halting further evaluations. This can be achieved more effectively with tuple types instead of a numeric literal type (using variadic tuple types for shortening tuples by one element, as there is no direct support for subtracting from a numeric literal type).

For instance:

type NestedKeyOf<T extends object, D extends any[] = [0, 0, 0, 0, 0, 0, 0, 0]> =
    D extends [any, ...infer DD] ? ({
        [K in keyof T & (string | number)]: T[K] extends object
        ? `${K}` | `${K}.${NestedKeyOf<T[K], DD>}`
        : `${K}`
    }[keyof T & (string | number)]) : never;

In this scenario, the D type argument is checked for elements. If empty, NestedKeyOf<T, []> results in never, preventing recursion. Otherwise, the first element of D is removed, leaving DD, and subsequent calls use NestedKeyOf<T[K], DD>.

I've set a default type argument of [0, 0, 0, 0, 0, 0, 0, 0] for D. Therefore, using NestedKeyOf<T> without D allows up to eight levels of recursion. Adjustments can be made for deeper or shallower limits, bearing in mind that exceeding depth may lead to type instantiation warnings.


Let's apply it to Circular:

type Z = NestedKeyOf<Circular>
/* Output Omitted for Brevity */

The recursion stops after eight levels in the example above, evident by absence of longer key paths beyond

"self.self.self.self.self.self.self.self"
.

Playground Link for Code Demo

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

What is TypeScript's approach to managing `data-*` attributes within JSX?

Let's consider the following code snippet: import type { JSX } from 'react'; const MyComponent = (): JSX.Element => ( <div data-attr="bar">Foo</div> ); Surprisingly, this code does not result in any TypeScript er ...

Encountering npm3 installation errors with typyings in Angular 2?

Each time I try to sudo npm install Angular 2 modules, everything updates and installs correctly. However, I encounter the following error when the typings install is attempted: <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="0 ...

Encountering an issue during the initialization of the Google Passportjs

I recently made the switch from JavaScript to TypeScript in my server project and I'm currently tidying up some code. I decided to combine my Google Passport OAuth stuff and login routes into a single file, but it seems like I've broken something ...

Retrieving the key from an object using an indexed signature in Typescript

I have a TypeScript module where I am importing a specific type and function: type Attributes = { [key: string]: number; }; function Fn<KeysOfAttributes extends string>(opts: { attributes: Attributes }): any { // ... } Unfortunately, I am unab ...

Learn how to manually trigger the opening of ngx-popover in Angular 2

I have implemented ngx-popover in my project. I am attempting to trigger it from a different component using a button click. Second Component HTML: <button popover #ChatPopover=popover (click)="ClickElement()"> <span class="glyphicon glyphico ...

The TypeScript `unknown` type restricts the use of non-unknown types in function parameters

Why is there an error in this code? const x: unknown[] = ['x', 32, true]; // OK const y: (...args: unknown[]) => unknown = (xx: number) => {}; // ERROR // Type '(xx: number) => void' is not assignable to type '(...args: u ...

Unable to pass response from httpclient post method to another custom function in Angular 4

I've implemented the addUser(newUser) function in my sign-in.service.ts file like this: addUser(newUser) { const httpOptions = { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }; let body = JS ...

How can I implement a button in Angular Ag Grid to delete a row in a cell render

I have included a button within a cell that I want to function as a row deleter. Upon clicking, it should remove the respective row of data and update the grid accordingly. Check out the recreation here:https://stackblitz.com/edit/row-delete-angular-btn-c ...

How to achieve dynamic class instantiation through constructor injection in Angular 8?

Despite my efforts to find a solution, my understanding of Dependency Injection in services is still limited, making it challenging to get this thing working. I'm left wondering if there's any way to make it work at all. My current setup involve ...

The input '{ data: InvitedUser[]; "": any; }' does not match the expected type 'Element'

I'm currently facing a typescript dilemma that requires some assistance. In my project, I have a parent component that passes an array of results to a child component for mapping and displaying the information. Parent Component: import { Table } fr ...

Incorporating an offset with the I18nPluralPipe

Having trouble with my multiselect dropdown and the text pluralization. I attempted to use the I18nPluralPipe, but can't seem to set an offset of 1. ListItem = [Lion, Tiger, Cat, Fox] Select 1 Item(Tiger) = "Tiger", Select 3 Item(Tiger, Cat, Fox) = ...

There is no initial value set for the property and it is not definitively assigned in the constructor

I encountered an issue while working on the following code snippet: export class UserComponent implements OnInit { user: User; constructor() { } ngOnInit() { this.user = { firstName : "test", lastName ...

Incorporate typings into your CDN integration

I'm interested in utilizing a CDN to access a JSON validation library, as it's expected to provide faster performance (due to retrieving the file from the nearest server within the CDN). The JSON validation library in question can be found here: ...

What is the most effective way to move specific data from one page to another in Angular/Typescript?

Welcome to my Main Page! https://i.stack.imgur.com/m9ASF.png This is where I want to start my journey. https://i.stack.imgur.com/E8pAW.png My goal is to click the Last 1 Day button to redirect to another page with the date filter and ItemId values already ...

Angular time-based polling with conditions

My current situation involves polling a rest API every 1 second to get a result: interval(1000) .pipe( startWith(0), switchMap(() => this.itemService.getItems(shopId)) ) .subscribe(response => { console.log(r ...

Unable to load class; unsure of origin for class labeled as 'cached'

Working on an Angular 10 project in visual studio code, I've encountered a strange issue. In the /app/_model/ folder, I have classes 'a', 'b', and 'c'. When running the application in MS Edge, I noticed that only classes ...

Tips for efficiently deconstructing JSON arrays, objects, and nested arrays

I'm attempting to destructure a JSON file with the following structure: [ { "Bags": [ { "id": 1, "name": "Michael Kors Bag", "price": 235, "imgURL" ...

React: Avoid unnecessary re-rendering of child components caused by a bloated tree structure

I am dealing with a tree/directory structured data containing approximately 14k nodes. The issue I am facing is that every time a node is expanded or minimized by clicking a button, causing it to be added to an 'expanded' Set in the Redux state, ...

Discovering the quantity of items with a specific value in Angular 8

I'm attempting to determine the number of objects with a status value of 'Served', which should yield 2. I'm unsure about the method I should use to achieve this. Any suggestions on which method would be best? {full_name: 'Jenny&a ...

How can I properly customize and expand upon a Material UI ListItem component?

I'm currently working with TypeScript version 3.4.5 and Material UI version 4.2. I have the following code snippet: interface MyItemProps { name: string; value: string; } function Item({ name, value, ...props }: ListItemProps<'li&apo ...