Retrieving a nested type based on a particular condition while keeping track of its location

Given an object structure like the one below:

type IObject = { id: string, path: string, children?: IObject[] }

const tree = [
    {
      id: 'obj1' as const,
      path: 'path1' as const,
      children: [
        {
          id: 'obj2' as const,
          path: ':pathParam' as const,
          children: [
            {
              id: 'obj3' as const,
              path: 'path3' as const
            }
          ]
        },
        {
          id: 'obj4' as const,
          path: 'path4' as const,
          children: [
            {
              id: 'obj5' as const,
              path: 'path5' as const
            }
          ]
        }
      ]
    }
  ];

I am attempting to retrieve the type of a nested object AND an accumulation of the path fragments leading to that nested type. (assuming unique IDs across the object) example

type ok = FindAndTrackDeepPath<'obj2', typeof tree>['fullPath'];
// type ok = "/path1/:pathParam"

I have tried a solution but it's not working as expected. When recursion occurs, it creates a union of all options at each level, causing the issue demonstrated in the second example.

export type FindAndTrackDeepPath<
  ID,
  T extends IObject[],
  depth extends number = 1,
  path extends string = '',
  maxDepth extends number = 10
> = ...

The problem arises when attempting to filter out unwanted descendents while recursing. This leads to incorrect paths being generated.

Upon research, I found similar questions here and here, but they extract all possible paths instead of specific ones that match particular criteria.

If you can provide any assistance, I'd greatly appreciate it. Is the desired outcome achievable?

TS Playground example

Answer №1

Below is an example implementation of a concept known as IdxWithFullPath<I, T>. In this implementation, I represents the generic identifier type that can be found within the IObject-or-IObject[] type T. The result will be an intersection of the IObject that has an id assignable to type I, and an object with a fullPath property that combines all the path properties in the tree starting from the root down to the specified IObject:

type IdxWithFullPath<I, T, P extends string = ""> =
  T extends IObject[] ? IdxWithFullPath<I, T[number], P> :
  T extends IObject ? (
    I extends T["id"] ? (
      T & { fullPath: `${P}/${T["path"]}` }
    ) : (
      IdxWithFullPath<I, T["children"], `${P}/${T["path"]}`>
    )
  ) :
  never;

The implementation includes a third generic type parameter P, representing the parent path. This allows for the recursive nature of IdxWithFullPath. By default, P is set to an empty string literal type, indicating the root path of the tree.

It's important to note that this is a distributive conditional type in the context of T. This means that if T is a union, it will be processed individually and then combined back into a union. If a specific type result is not needed, never can be returned for that case.

When testing this implementation with different types, make sure to account for potential edge cases. Extensive testing against expected scenarios is essential to ensure the implementation's correctness and robustness.


Feel free to test the implementation using the examples provided:

type Ok = IdxWithFullPath<'obj2', typeof tree>;
/* type Ok = {
    id: "obj2";
    path: ":pathParam";
    children: {
        id: "obj3";
        path: "path3";
    }[];
} & {
    fullPath: "/path1/:pathParam";
} */

type AlsoOK = IdxWithFullPath<'obj3', typeof tree>;
/* type AlsoOK = {
    id: "obj3";
    path: "path3";
} & {
    fullPath: "/path1/:pathParam/path3";
} */

Upon testing, ensure thorough evaluation of the behavior of the implementation, especially in complex or niche scenarios. As with any intricate type implementation, be prepared to refine or reconsider the approach based on the outcome of testing.

Access the Playground link here

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 the best way to implement promise function in a JavaScript functional method such as forEach or reduce?

I have implemented a promise function in the following way: // WORK let res = {approveList: [], rejectList: [], errorId: rv.errorId, errorDesc: rv.errorDesc}; for (let i = 0; i < rv.copyDetailList.length; i ++) { const item = rv.copyDetailList[i]; ...

Guide on how to prevent click events when a checkbox is not selected in Angular 2

A click event is being used on a ul element with a checkbox below it. When the checkbox is checked, the list should be enabled and the click event should work. If the checkbox is unchecked, the list should be disabled and the click event should not work. ...

Tips on how to retrieve an Observable Array instead of a subscription?

Is there a way to modify this forkJoin function so that it returns an observable array instead of a subscription? connect(): Observable<any[]> { this.userId = this.authService.userId; this.habits$ = this.habitService.fetchAllById(this.userId); this.s ...

Uploading files in Angular 5 with additional properties of other objects

I am facing a challenge with uploading a file as part of a property to an object within a form. Most documentations I have come across only focus on services that handle standalone files. In my case, I have a form with various text inputs and date pickers, ...

Transforming a string such as "202309101010" into a date entity

Need to convert a string in the format "YYYYMMDDHHMM" (e.g. "202309101010") into a Date object in TypeScript? Check out this code snippet for converting the string: const dateString: string = "202309101010"; const year: number = parseInt(dateString.subst ...

Tips for creating fixed first two columns in a table using React and TypeScript

I need a table where the first two columns stay fixed as headers while scrolling through the body of the table. ...

Enhancing validation in Express with custom Typescript types for validation in Express Validator

I encountered an error while using the custom method of the express validator Issue: Argument of type '(userDoc: User | null) => Promise<never> | undefined' is not assignable to parameter of type '(value: User | null) => Promise ...

What is the implication when Typescript indicates that there is no overlap between the types 'a' and 'b'?

let choice = Math.random() < 0.5 ? "a" : "b"; if (choice !== "a") { // ... } else if (choice === "b") { This situation will always be false because the values 'a' and 'b' are completely disti ...

Troubleshooting issues with sorting and pagination in Angular Material table functionality

I am experiencing an issue with sorting and pagination using an Angular material table. The data is being fetched from a store as an observable and successfully displayed in the table. Even though the column names for sorting match the column definitions, ...

Alternative for using useRouteMatch to retrieve parameters

I'm currently refactoring this code and struggling to find a suitable replacement for this section. This is due to the react-router-dom v6 no longer having the useRouteMatch feature, which I relied on to extract parameters from the URL: import React, ...

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": " ...

Unable to modify the Jest mock function's behavior

The issue I am facing involves the following steps: Setting up mocks in the beforeEach function Attempting to modify certain mock behaviors in specific tests where uniqueness is required Encountering difficulty in changing the values from the in ...

Error message in TypeScript: A dynamic property name must be a valid type such as 'string', 'number', 'symbol', or 'any'

Attempting to utilize the computer property name feature in my TypeScript code: import {camelCase} from "lodash"; const camelizeKeys = (obj:any):any => { if (Array.isArray(obj)) { return obj.map(v => camelizeKeys(v)); } else if (ob ...

Troubleshooting Angular 2: Why Array Interpolation is Failing

Greetings everyone, I am diving into Angular 2 and attempting to create a basic Todo application. Unfortunately, I've hit a roadblock. My array interpolation seems to be malfunctioning. Any assistance would be greatly appreciated. Here is my AppCompo ...

Angular 8 throwing an ExpressionChangedAfterItHasBeenCheckedError when a method is called within the ngOnInit function

I encountered a common issue with an Angular template. I have a standard template for all my pages, containing a *ngIf directive with a spinner and another one with the router-outlet. The behavior and visibility of the spinner are controlled by an interce ...

How can I design a Typescript interface that accommodates both strings and other data types?

I am working on designing an interface that allows for an array of objects and strings to be stored. For instance: const array = [ '', {id: '', labels: ['']} ] I attempted to achieve this using the following code: export ...

"Integrating Orgchart with Typescript in Angular4: A Step-by-Step

mxResources.loadDefaultBundle = false; var bundle = mxResources.getDefaultBundle(RESOURCE_BASE, mxLanguage) || mxResources.getSpecialBundle(RESOURCE_BASE, mxLanguage); // Ensures synchronous requests are handled properly ...

Exploring the synergies between Typescript unions and primitive data types in

Given the scenario presented interface fooInterface { bar: any; } function(value: fooInterface | string) { value.bar } An issue arises with the message: Property 'bar' does not exist on type '(fooInterface | string)' I seem ...

Transforming a material-ui component from a class to a function

Currently, I am in the process of learning material-ui, and it seems that most of the code examples I come across are based on classes. However, the new trend is moving towards using functions instead of classes due to the introduction of hooks. I have be ...

Determine the output of a function based on the structure of the input parameter by mapping through a complex nested object

Trying to implement some intricate typing for a project I'm developing, and wondering if it's achievable with TypesScript. The project in question is a form generator based on schemas and promises, using Vue and TS. It handles UI rendering, vali ...