Transforming a recursive array or object into a flattened object type using TypeScript

I am currently working on creating a function that can handle a recursive object/array structure where each node contains a "name" and optionally, "children". My goal is to have a function that transforms this recursive structure into a type-safe object. The keys of the new object will be the recurring "name"s, ensuring compile-time errors if an invalid key is accessed.

So far, I have managed to recognize the top-level names (names "a" and "b") in such a way that 'flat' would be identified as

Record<"a" | "b", RouteConfigItem<"a" | "b">>
.

type RouteConfigItem<Keys> = {
    name: Keys;
    path: string;
    children?: Array<RouteConfigItem<Keys>>;
}

type RouteConfig<Keys> = RouteConfigItem<Keys>[];

function getFlat<Keys>(routeConfig: RouteConfig<Keys>): Record<Keys, RouteConfigItem<Keys>> {
    // Implementation details are not crucial at the moment.
    return routeConfig as Record<Keys, RouteConfigItem<Keys>>;
}

const flat = getFlat([{
    name: 'a',
    path: 'a',
}, {
    name: 'b',
    path: 'b',
    children: [{
        name: 'c',
        path: 'c',
    }]
}] as const);

My challenge now lies in figuring out how to extend this functionality to include non-top-level names as well. Striving for proper types without focusing on implementation specifics within the body of getFlat(), I aim for flat to be recognized as

Record<"a" | "b" | "c", RouteConfigItem<"a" | "b" | "c">>
.

Just a note, while this code example seems to work in my WebStorm environment, it does not function in typescriptlang.org/play, hence the reason for not providing a link.

Answer №1

I have simplified the RouteConfig definition for better understanding:

type RouteConfig = {
    name: string, 
    path: string, 
    children?: ReadonlyArray<RouteConfig> 
};

To extract keys now, follow these steps:

type ExtractKeys<R extends RouteConfig> = R extends { children: ReadonlyArray<RouteConfig> }
    ? R['name'] | ExtractKeys<R['children'][number]>
    : R['name'];

The function signature will look like this:

function getFlat<R extends RouteConfig>(routeConfig: readonly R[]):
    Record<ExtractKeys<R>, RouteConfig> {

    // The implementation is not important at this point.
    return routeConfig as any;
}

const flat = getFlat([{
    name: 'a',
    path: 'a',
}, {
    name: 'b',
    path: 'b',
    children: [{
        name: 'c',
        path: 'c',
    }]
}] as const); // Record<"a" | "b" | "c", RouteConfig>

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

The @Input directive is failing to receive any data from its parent component

Recently, I delved into the workings of Angular's @Input feature and have found it quite useful thus far. My database is Firebase, and the data snippet I am fetching looks like this: { "page_area_business_image" : { "expand" : { ...

Challenges arise with data updating following a mutation in @tanstack/react-query

As I work on building an e-commerce website using React, I have a specific feature where users can add products to their favorites by clicking a button. Following this action, I aim to update the profile request to display the user's information along ...

Creating personalized HTTP status codes within tsoa or any other similar framework

I was able to define custom status codes (such as 600) with TSOA in the past (v3.5.2), but it seems like the latest versions don't support this anymore. Considering that TSOA follows the OpenAPI specification, which only allows certain status codes li ...

Error when attempting to add data into MongoDB using Node.JS: "The type 'string' cannot be assigned to type 'ObjectId | undefined'."

Attempting to add a document to the collection results in an error when specifying the _id field of the added document. How can I insert a document with an _id that is not an ObjectId? The error occurs with the following code. Omitting the _id resolves th ...

Exploring the utilization of an interface or class in Typescript

Imagine a common situation where users need to provide an email and password for logging in using Typescript. To make this process more organized, I want to define a strong type that represents the user's login information and send it securely to the ...

Angular device redirection allows you to automatically redirect users based on the device

Currently in my Angular project, I am attempting to dynamically redirect users based on their device type. For example, if the user is on a Web platform, they will be redirected to www.web.com. If they are on an Android device, they should go to www.androi ...

Merging an assortment of items based on specific criteria

I have the following TypeScript code snippet: interface Stop { code: string } interface FareZone { name: string; stops: Stop[]; } const outbound: FareZone[] = [{name: 'Zone A', stops: [{ code: 'C00'}] }, {name: 'Zone B ...

How can Observables be designed to exhibit both synchronous and asynchronous behavior?

From: Understanding the Contrasts Between Promises and Observables In contrast, a Promise consistently operates asynchronously, while an Observable can function in synchronous or asynchronous manners. This presents the opportunity to manipulate code in ...

TS will not display an error when the payload is of type Partial

Why doesn't TypeScript throw an error when making the payload Partial? It seems to only check the first value but not the second one. type UserState = { user: User | null; loading: boolean; error: Error | null } type UserAction = { type: type ...

The scrolling experience in Next js is not as smooth as expected due to laggy MOMENTUM

Currently, I am in the process of constructing my portfolio website using Next.js with Typescript. Although I am relatively new to both Next.js and Typescript, I decided to leverage them as a learning opportunity. Interestingly, I encountered an issue with ...

Executing observables consecutively in Angular without delay

Here are the service calls that I have available: productService.GetAllProducts() productService.DeleteProduct() productService.GetCategories() productService.DeleteCategory() In pseudo code, I need to perform the following steps in my component: ...

What method can I use to prevent users from choosing file types other than the specified ones when using the input type file in React with TypeScript?

I am looking to limit users from choosing files with extensions other than .xml in the select dialog window. Currently, my code looks like this: <input type='file' accept='.xml' onChange={handleselectedfile}/> However, users ca ...

Remove items from the array that are also found in another array

I am currently working with two arrays structured as follows: this.originalArray = [{ id: 10, name: 'a', roleInfo: [{ roleID: 5, roleName: 'USER' }] }, { id: 20, name: 'b', roleInfo ...

What is the process for generating a new type that includes the optional keys of another type but makes them mandatory?

Imagine having a type like this: type Properties = { name: string age?: number city?: string } If you only want to create a type with age and city as required fields, you can do it like this: type RequiredFields = RequiredOptional<Propertie ...

Updating the DOM with an EventListener in Angular 5 is not functioning properly

Situation : Utilizing an Angular PWA for communication with an iOS native app via WKWebview. Implementing messageHandlers to facilitate data sharing between TypeScript and Swift logic code. Issue : Employing addEventListener to monitor a specific event on ...

It appears that protractor-flake is programmed to re-run all tests instead of just the ones that have failed

Running tests using the latest version of "[email protected]", encountering failures during testing but the tests are rerunning again. No custom reporter used except Allure reporting. Below is the command used for running: protractor-flake --max-at ...

Encountering issues following the integration of @angular/flex-layout into an Angular project

After careful consideration, I opted to utilize the responsive grid system provided by @angular/flex-layout instead of Bootstrap. By simply installing the npm package and adding it to my AppModule, I was able to integrate it seamlessly: import { NgModule ...

What steps can be taken when encountering TS errors regarding missing required fields that are in the process of being filled?

In a typical scenario, the process involves creating an empty object and populating it with the necessary data. Initially, this object does not contain any properties, which leads to TypeScript flagging an error since it lacks the required type. For insta ...

Why is it that TypeScript does not issue any complaints concerning specific variables which are undefined?

I have the following example: class Relative { constructor(public fullName : string) { } greet() { return "Hello, my name is " + fullName; } } let relative : Relative = new Relative("John"); console.log(relative.greet()); Under certain circum ...

Optimizing your use of fromCharCode.apply with Uint8Array in TypeScript 3

I recently came across some code that I inherited which appears like this: String.fromCharCode.apply(null, new Uint8Array(license)); Recently, we updated our project dependencies to TypeScript 3, which raised an error stating: Argument of type 'Ui ...