Guide on creating a TypeScript generic type for accessing nested properties and resolving their types

How can I develop a generic type that resolves to the data type of a nested property?

Assume I have a configuration structure

type Config = {
  parent: {
    child: boolean
  }
}

I am looking for something like

type ChildConfigType = NestedDataType<Config, 'parent.child'> // boolean
type ParentConfigType = NestedDataType<Config, 'parent'> // {child: boolean}

I already have a simple implementation for non-nested types

function getProperty<
    Type extends object,
    Key extends keyof Type
>(key: Key): Type[Key] {
    return {} as Type[Key]
}

getProperty<Config>('parent')

Furthermore, I have utility types for non-nested properties that are functional

type PropertiesOf<
    Type extends object
> = keyof Type

type TypeOfProperty<
    Type extends object,
    Key extends PropertiesOf<Type>
> = Type[Key]

function getPropertyWithUtility<
    Type extends object,
    Key extends PropertiesOf<Type>
>(key: Key): TypeOfProperty<Type, Key> {
    return {} as TypeOfProperty<Type, Key>
}

There is also a helper called NestedPropertiesOf that generates a union of nested properties

type NestedPropertiesOf<Type extends object> =
  {
    [key in keyof Type & (string | number)]: Type[key] extends object
    ? `${key}` | `${key}.${NestedPropertiesOf<Type[key]>}`
    : `${key}`
  }[keyof Type & (string | number)]

type NestedConfigProperties = NestedPropertiesOf<Config> // 'parent' | 'parent.child'

My goal is to implement a function with the following signature

function getNestedProperty<
    Type extends object,
    Key extends NestedPropertiesOf<Type>
>(key: Key): NestedTypeOfProperty<Type, Key> {
    return {} as NestedTypeOfProperty<Type, Key>
}


getNestedProperty<Config>('parent.child')

I'm beginning to think this might not be feasible because TypeScript types cannot directly manipulate strings, making it impossible to remove the first segment of the path.

Try it on TypeScript Playground

Answer №1

Utilizing template literals and the infer keyword in TypeScript allows for string manipulation.

Here are some helpful utilities:

Dot is a function used to combine two strings with a dot (.):

type Dot<T extends string, U extends string> = "" extends U ? T : `${T}.${U}`;

StopFields represents the types where operations should stop, such as primitives like strings, numbers, booleans, and symbols:

type StopFields = string | number | boolean | symbol;

PathToFields provides all potential paths to properties of a given type using mapped types, excluding arrays unless specified otherwise:

type PathsToFields<T> = T extends StopFields
  ? ""
  : {
      [K in Extract<keyof T, string>]: NonNullable<T[K]> extends Array<any>
        ? never
        : K | Dot<K, PathsToFields<T[K]>>;
    }[Extract<keyof T, string>];

To implement this concept:

type NestedType<T, Path extends PathsToFields<T>> = Path extends keyof T
  ? T[Path]
  : Path extends `${infer Property extends keyof T & string}.${infer SubField}`
  ? SubField extends PathsToFields<T[Property]>
    ? NestedType<T[Property], SubField>
    : never
  : never;

The NestedType function involves a type and its corresponding field paths. It checks if the path exists within the object's properties, returning the type if so; otherwise, it breaks down the path using dots (.) to isolate the key and remaining path segments for further processing.

Example usage:

type ChildConfigType = NestedType<Config, "parent.child">; // returns boolean
type ParentConfigType = NestedType<Config, "parent">; // returns {child: boolean}

Try it out on the online 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

Unable to retrieve the specific value associated with a key from JSON data

I am attempting to retrieve the value of "id" from a JSON response I received after making a POST request. { "callId": "87e90efd-eefb-456a-b77e-9cce2ed6e837", "commandId": "NONE", "content": [ { "scenarioId": "SCENARIO-1", "Channel": " ...

Is there a way to enable code completion for Firebase on VS Code?

After successfully setting up Typescript for code completion following the guidelines provided in this resource, I now want to enable code completion for Firebase in VS Code. However, I am unsure of the steps to achieve this. How can I activate code compl ...

Issue Error: NG0201: NgControl provider not found within NodeInjector

My creativity has hit a roadblock and I'm looking for some help. I decided to use the Reactive Forms Module in Angular, so I imported it into my app.module.ts as shown below: import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ ...

Developing Angular PWAs with a focus on microfrontends

I have set up multiple microfrontends using an "app-shell" type of application for the domain root, with each microfrontend on the first path element. Each app is constructed as a standalone angular application utilizing shared libraries to reuse common co ...

The 'log' property cannot be found on the type '{ new (): Console; prototype: Console; }' - error code TS2339

class welcome { constructor(public msg: string){} } var greeting = new welcome('hello Vishal'); Console.log(greeting.msg); I encountered an error at the Console.log statement. The details of the error can be seen in the image attached below. ...

Issue arises when isomorphic-dompurify is used alongside dompurify in Next.js 13 causing compatibility problems

I am currently facing a compatibility problem involving isomorphic-dompurify and dompurify in my Next.js 13 project. It appears that both libraries are incompatible due to their dependencies on canvas, and I am struggling to find a suitable alternative. M ...

Encountering an issue while developing a Discord bot using TypeScript

Hello, I'm currently working on creating a nick command for my discord bot in TypeScript, but I encountered an error. Here is the issue: Error: Expression expected.ts (1109) When I replace const mentionedMember = message? message.mentions.members? ...

Adding a class to a child component layout from a parent component in Angular 12 and Typescript can be achieved by using the ViewChild decorator

Incorporating the child component into the parent component is an important step in the structure of my project. The dashboard component serves as the child element, while the preview component acts as the parent. Within the parent (preview) component.htm ...

Unable to upload any further verification documents to Stripe Connect bank account in order to activate payouts

Query - Which specific parameters should I use to generate the correct token for updating my Stripe bank account in order to activate payouts? I've attempted to activate payouts for my Stripe bank account by using a test routing and account number (t ...

What is the process for defining the root of a project in ESLint?

I've been working on a project using Next.js and Typescript. My imports look like this: import Component from "/components/Component/Component";, with the root directory being specified as /src. This setup works fine in Next.js, but ESLint k ...

Tips for sending a query using the http GET method in Next.JS 14 API routes

When using the Next.js 14 API route, I am passing a page in the GET method to paginate the data fetched from my database. However, an error is thrown when trying to retrieve the query from the request: Property 'query' does not exist on type &a ...

What is the purpose of exporting both a class and a namespace under the same name?

While exploring some code, I came across a situation where a class and a namespace with identical names were exported from a module. It seems like the person who wrote this code knew what they were doing, but it raised some questions for me. Could you shed ...

Changing the type of value in a React select onChange

<Select options={options} value={selectedBusinessList} isMulti placeholder="Select Business" onChange={(value: any) => setSelectedBusinessList(value)} onInputChange={query => { if ...

Performing optimized searches in Redis

In the process of creating a wallet app, I have incorporated redis for storing the current wallet balance of each user. Recently, I was tasked with finding a method to retrieve the total sum of all users' balances within the application. Since this in ...

Ways to retrieve a value from outside the Angular subscribe block

Custom Template <div class="row" *ngFor="let otc of this.jsonData;index as j"> <div> <table class="table table-striped table-fixed"> <tr *ngFor="let opc of this.winServiceInfo ...

Encountering a TS2307 error while trying to import external modules into a TypeScript file

I recently added a new module using npm and now I'm trying to use it in my typescript file. npm install marker-animate-unobtrusive --save import SlidingMarker = require('marker-animate-unobtrusive'); Unfortunately, when I try to access th ...

Is it possible to create a prototype function within an interface that can group items in an array by a specific property, resulting in an array of objects containing a key and corresponding array of values?

I've been working on this code snippet and I'm trying to figure out how to make it work: Array<T>.groupBy<KeyType> (property): {key: KeyType, array: Array<T> }[]; The code looks like this: type ArrayByParameter<T, KeyType = ...

Typescript Error: Issue encountered while passing props. Unable to access properties as they are undefined

I encountered an issue where I created an object of a certain type and attempted to pass it to a component. However, when passing the props, I received an error message stating that it cannot read properties of undefined ('stepOne'). The error sp ...

How to delay setting a property in Angular 2 until the previous setter has finished execution

Hey there, I'm facing an issue with my component. Within my component, I have an input setter set up like this: @Input() set editStatus(status: boolean) { this.savedEditStatus = status; if (this.savedEditStatus === true && this.getTrigg === t ...

Exploring Improved Methods for Implementing Nested Subscriptions in Typescript

In my Typescript code for Angular 11, I am working with two observables. The first one, getSelfPurchases(), returns data objects containing information like id, user_id, script_id, and pp_id. On the other hand, the second observable, getScriptDetails(32), ...