How to retrieve a union type of keys from a nested object in TypeScript

My attempt to extract types of keys from nested objects led me to try the following approach.

Check out TS Playground

type RecursiveRecord = {
  [key in string]: string | RecursiveRecord;
};

type Keys<T extends RecursiveRecord, K = keyof T> = K extends string
  ? T[K] extends string
    ? K
    : T[K] extends RecursiveRecord
    ? K | Keys<T[K]> // encountered an error here
    : never
  : never;

type Obj = {
  a: {
    c: 'aaaaaa';
    d: 'aaaaaaaaaaa';
    e: { f: 'q' };
  };
  b: 'dd';
};

export type A = Keys<Obj>; // expecting to retrieve "a" | "b" | "c" | "d" | "e" | "f"

However, I faced a type error with K | Keys<T[K]>:

index.ts:9:16 - error TS2344: Type 'T[K]' does not satisfy the constraint 'RecursiveRecord'.
  Type 'T[string]' is not assignable to type 'RecursiveRecord'.
    Type 'string | RecursiveRecord' is not assignable to type 'RecursiveRecord'.
      Type 'string' is not assignable to type 'RecursiveRecord'.

Answer №1

A problem highlighted in TypeScript's GitHub repository is the lack of tracking constraints on conditional types, specifically more complicated checked types like T[K]. The issue at hand is currently awaiting more feedback, so it is crucial for users to show support and provide compelling use cases if they wish to see any changes implemented. While there may not be a definitive GitHub issue dedicated to this issue yet, it remains an aspect of the language that needs attention.

In situations where the compiler overlooks certain constraints that should be recognized, a helpful strategy is to "remind" it by utilizing the Extract utility type. By replacing XXX with Extract<XXX, YYY>, users can ensure that the compiler acknowledges the assignability of Extract<XXX, YYY> to YYY.

To resolve errors, a code snippet such as:

type Keys<T extends RecursiveRecord, K = keyof T> =
  K extends string ? (
    T[K] extends string ? K :
    T[K] extends RecursiveRecord ? K | Keys<Extract<T[K], RecursiveRecord>> :
    never
  ) : never;

can be quite beneficial.


Alternatively, another approach could be:

type NestedKeys<T> =
  T extends object ? { [K in keyof T]-?: K | NestedKeys<T[K]> }[keyof T] : never;

which results in a similar outcome as demonstrated in your example:

type B = NestedKeys<Obj>
// type B = "a" | "b" | "c" | "d" | "e" | "f"

Explore the code on TypeScript Playground

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

Issue with Angular data display in template

My Ionic app with Angular is fetching data in the constructor, but I am facing difficulties displaying it in the HTML. Code component receiver: any; constructor( //.... ) { // get receiver data const receiverData = this.activatedRoute.snapsho ...

Spacing Problem with Title Tooltips

After using the padEnd method to ensure equal spacing for the string and binding in the title, I noticed that the console displayed the string perfectly aligned with spaces, but the binded title appeared different. Is it possible for the title to support s ...

TypeScript encountering issues when creating objects within the realm of Jest

I'm in the process of testing my React application using Jest. When I initiate the command to run the tests, jest, an error is thrown stating: Debug Failure. False expression: Output generation failed 1 | import * as TestData from 'TestMo ...

Issue with TypeScript: "Cannot find name" error encountered during yarn build

I'm encountering an issue with my .tsx component code below: const Header = ({ dict: map, lang: string }) => { return ( <header className="flex items-center justify-between py-10"> <div> <Link href={`/${ ...

Most effective method for *overriding* TypeScript types from third party libraries in the node_modules directory

Often, third-party type definitions included in a dedicated @types/* package or alongside an npm package are discovered to be incomplete or incompatible with new dependencies. Thus, I am searching for an easy way to override the types specified in node_mo ...

Utilizing an API to dynamically augment a JSON object with user input in Angular

Hello, I am working on adding an input value to an object property. The scenario is that a customer wants to add an item to their shopping cart, but before adding the item, they need to choose the size. I have the form set up with validation and can retri ...

Service error: The function of "method" is not valid

In one of my Angular 2 applications, I have a class that contains numerous methods for managing authentication. One particular method is responsible for handling errors thrown by the angular/http module. For example, if a response returns a status code o ...

How can I access the parameter value for the tooltip callback function within echarts?

I am attempting to retrieve the value for this specific Apache EChart from within the callback function of the tooltip formatter. When I manually input the value, the formatting function operates correctly: formatter: (params:any) => `$ ${Math.round(pa ...

Ways to access an observable's value without causing a new emit event

Is there a way to retrieve the value of an observable without causing it to emit again? I need this value for an ngClass expression. I attempted to use the tap operator within the pipe to access the values in switchMap, but the value is not being logged. ...

"Creating a backend server using Node.js, TypeScript, and g

I am currently in the process of developing a nodejs project that will consist of 3 key services: Gateway Product Order The Product and Order services will perform functions related to their respective names, while the Gateway service will take JSON requ ...

Tips for retrieving corresponding values from a TypeScript dictionary object?

I am currently working with a dictionary object that is filled in the following manner: const myDictionaryElement = this.myDictionary["abc"]; In this case, myDictionaryElement contains the values: ACheckStatus: "PASS" QVVStatus: "READY" VVQStatus: "READ ...

Display a React component according to the user's input

Within the first (parent) div, there is an underlined message stating: "This JSX tag's 'children' prop expects a single child of type 'ReactNode', but multiple children were provided.ts(2746)". import A from './components/A&ap ...

Modify the selected toggle buttons' color by utilizing the MUI ThemeProvider

I am currently working on customizing the color of selected toggle buttons within my React app using TypeScript and ThemeProvider from @mui/material 5.11.13. Despite my efforts, when a toggle button is selected, it still retains the default color #1976d2, ...

Unable to assign to 'disabled' as it is not recognized as a valid attribute for 'app-button'

How to link the disabled property with my button component? I attempted to add 'disabled' to the HTML file where it should be recognized as an input in the button component (similar to how color and font color are recognized as inputs) ... but ...

Example of TypeScript Ambient Namespace Usage

The Namespaces chapter provides an example involving D3.d.ts that I find puzzling. Here is the complete example: declare namespace D3 { export interface Selectors { select: { (selector: string): Selection; (element: ...

Update the value within an object Array using a for loop

In JavaScript, I am looking to update a value in an array of objects. export var headingLeistenbelegung = [ [ {value: 'Projektnummer: ', style: styles.headerDarkBold}, {value: '121466', style: styles.headerDark}, {value: &apo ...

Retrieve values from a multi-level nested object by using square bracket notation for each level

I am in need of a config object to display nested data. Check out the demo code for reference In the code, accessing "customer.something" is essential. There can be multiple levels of nesting, which the grid handles with field='customer.som ...

What is the proper way to nest a type alias array within another type alias in TypeScript code?

type subProperty = { identifier: string, label: string }; type MainParentProps = { subscriptionList?: [ { identifier: string, content: [???Collection Of subProperty???] } ] } Can this scenario be implemented in typescript usin ...

Resolving a persistent AngularJS 1 constant problem with Typescript

I'm currently developing an application using TypeScript and AngularJS 1, and I've encountered a problem while trying to create a constant and passing it to another class. The constant in question is as follows: module app{ export class A ...

Identifying Internet Explorer 11 within Angular 5 to effectively manage and eliminate "property not found" alerts

While working on a custom directive that requires scroll position, I discovered that all major browsers support window.scrollY, except for IE11 which needs document.documentElement.scrollTop. Therefore, I am attempting to check if the current browser is I ...