Concatenating keys recursively using TypeScript

Utilizing a firestore database, I have established a schema to structure my data:

type FirestoreCollection<T> = {
  documentType: T;
  subcollections?: {
    [key: string]: FirestoreCollection<object>;
  };
};

type FirestoreSchema<
  T extends { [key: string]: U },
  U = FirestoreCollection<object>
> = T;

export type FirestoreModel = FirestoreSchema<{
  'chat-messages': {
    documentType: ChatMessage;
  };
  chats: {
    documentType: Chat;
    subcollections: {
      'chat-participants': {
        documentType: ChatParticipant;
      };
    };
  };
}>;

This schema is functioning smoothly.

Now, I aim to devise a type that combines all the keys from the model, interconnected by a /, followed by a string (a document id), and then iterate recursively through all subcollections in the model.

The desired output for this type is as follows:

type DocumentPath =
  | `chat-messages/${string}`
  | `chats/${string}`
  | `chats/${string}/chat-participants/${string}`;

Although here's my current attempt at it, I sense there's still room for improvement:

type DocumentPath<
  T extends { [key: string]: U },
  U = FirestoreCollection<object>
> = {
  [key in keyof T]:
    | `${key & string}/${string}/${DocumentPath<T[key]['subcollections']>}`
    | `${key & string}/${string}`;
}[keyof T];

Any suggestions to enhance this? Feel free to share your thoughts.

Answer №1

Give this a try

type PathDocument<T> = {
  [K in keyof T & string]:
    | `${K}/${string}`
    | ('subcategories' extends keyof T[K]
        ? `${K}/${string}/${PathDocument<T[K]['subcategories']>}`
        : never);
}[keyof T & string];

The key thing to focus on here is the PathDocument type. It iterates over the string keys of the object and generates ${key}/string}, calling itself recursively if there are subcategories nested under the current key.

Example:

// type B = `blog-posts/${string}` | `blogs/${string}` | `blogs/${string}/post-comments/${string}`
type B = PathDocument<DatabaseSchema>;

Check out the 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

Type for handling event unions

My goal is to implement a type for an event handler that enables autocomplete functionality for event data. The events I need to handle have the following structure: type MyEvent = | { eventA: { foo: number; bar: number; }; } | { ...

TypeB should utilize InterfaceA for best practice

I have the following TypeScript code snippet. interface InterfaceA { id: string; key: string; value: string | number; } type TypeB = null; const sample: TypeB = { id: '1' }; I am looking for simple and maintainable solutions where TypeB ...

Having difficulty retrieving values while using async-await

Utilizing the code below has been successful for me. I managed to retrieve the data in the spread (then), returning a http200 response. Promise.all([ axios({ method: 'post', url: 'https://oauth2.-arch.mand.com/oauth2/token&a ...

Employing a one-time use variable that is asynchronously loaded via a React hook

Currently, I am exploring the functionalities of React's hooks, but I'm encountering a roadblock when it comes to integrating different use cases. The main goal is to create a hook called useNationsAsync that fetches a list of available nations ...

Converting JSON into TypeScript class or interface?

Currently, I am in the process of building a web application using Angular2 and Typescript. The JSON structure I have is as follows: { "homeMenu":{ "aname1":{ "title":"text", "route":"myroute" }, "aname2":{ "title":"tex ...

Which option is more beneficial for intercepting API data in Angular 6: interfaces or classes?

My API returns JSON data that is not structured the way I need it, so I have to make changes. { "@odata.context":"xxxxxx", "id":"xxxxxxxx", "businessPhones":[ ], "displayName":"name", "givenName":"pseudo", "jobTitle":null, "ma ...

Developing a TypeScript Library from Scratch

After following the instructions on this particular website, I have implemented the following JavaScript code: function A(id) { var about = { Version: "0.0.0.1", }; if (id) { if (window === this) { return new A(i ...

Designing architecture for NPM packages in TypeScript

I am currently developing multiple NPM packages using TypeScript and I am exploring the most effective strategies for supporting various target architectures. When compiling to ES3, there is extensive support but it comes with additional boilerplate for c ...

Tips for properly sending a JWT token

When working on my angular application, I encountered an issue while trying to send a jwt token as a header for request authorization. The error 500 was triggered due to the jwt token being sent in an incorrect format. Here is how I am currently sending it ...

A step-by-step guide on generating a TypeScript class using JSON data

Working with Angular and making a call to an external API. The JSON data is structured as follows: [ { "AccessGroupsIdList": [], "FirstName": "Greg", "LastName": "Tipton", ...

Enhance your Angular application with lazy loading and nested children components using named outlets

Let me start by explaining that the example provided below is a simplified version of my routes that are not functioning properly. I am working on an angular project, specifically a nativescript angular project, and I suspect the issue lies within Angular ...

After destructuring, useParams may sometimes return as undefined

I'm encountering an issue with undefined variables being returned even after destructuring when using useParams(). I've tried various solutions but none seem to work for me. const App = () => { return ( <div className="container"> ...

Observable doesn't respond to lazy loaded module subscriptions

I am trying to understand why my lazy loaded module, which loads the test component, does not allow the test component to subscribe to an observable injected by a test service. index.ts export { TestComponent } from './test.component'; export { ...

I encountered an issue while attempting to retrieve data using route parameters. It seems that I am unable to access the 'imagepath' property as it is undefined

Is there a way to access data when the page loads, even if it is initially undefined? I keep getting this error message: "ERROR TypeError: Cannot read properties of undefined (reading 'imagepath')". How can I resolve this issue? import { Compone ...

How can I transfer data between methods within Angular?

Help Needed: I have a service file with two methods, each having its own API. I want to call the getData method within the deleteData method. Can anyone guide me on how to achieve this? .service.file getData(): Promise<PagedResult<Sites>> { ...

Managing the backspace function when using libphonenumber's AsYouTypeFormatter

I've been attempting to integrate the google-libphonenumber's AsYouTypeFormatter into a basic input field on a web form. For each key pressed by the user, I feed it into the inputDigit method. However, I've encountered an issue where when th ...

Creating a unique Nest.js custom decorator to extract parameters directly from the request object

I am working with a custom decorator called Param, where I have a console.log that runs once. How can I modify it to return a fresh value of id on each request similar to what is done in nestjs? @Get('/:id') async findUser ( @Param() id: stri ...

Count duplicated values in an array of objects using JavaScript ES6

I am working on creating a filter for my list of products to count all producers and display them as follows: Apple (3) I have managed to eliminate duplicates from the array: ["Apple", "Apple", "Apple"] using this helpful link: Get all non-unique values ...

Incorporating a JavaScript npm module within a TypeScript webpack application

I am interested in incorporating the cesium-navigation JavaScript package into my project. The package can be installed via npm and node. However, my project utilizes webpack and TypeScript instead of plain JavaScript. Unfortunately, the package is not fou ...

What is the method for passing arguments in TypeScript functions?

Having an issue with a TypeScript method that I've written: createSomeData(args: { data: Data, helpfulInfo?: Info, context?: UIContext }): Promise<IDataModel>; The main problem I'm facing is that I can't seem to call it properly. I at ...