Simplify a user interface

I have a scenario where I need to dynamically derive the keys/fields of my type based on a generic type.

For instance:

Here are the container interfaces

export interface IUser {
    id: BigInt;
    name: string;
    balance: number;
    address: Address;
}
export interface Address {
    street: string;
    city: string;
    zipcode: number;
    tags: string[];
}

I would like to create a type that looks like this

const matcher: Matcher<IUser> = {
  id: 1
}

This can be achieved using the following implementation of Matcher

export type Matcher<T> = {
    [K in keyof T]: (number | string | boolean)
}

However, for a more complex situation like

const deepMatcher: Matcher<IUser> = {
  id: 1,
  user.address.city: 'San Francisco'
}

How can I modify my model (Matcher) to accommodate this use-case?

Answer №1

Disclaimer: There are numerous edge cases with these types of problems. While this solution works for the provided example, it may not handle optional properties or unions well. Additionally, paths of objects inside arrays are not included in this solution.

This solution can be divided into two main parts:

Firstly, we define a type that takes a type T and generates a union of all paths within T.

type AllPaths<T, P extends string = ""> = {
    [K in keyof T]: T[K] extends object 
      ? T[K] extends any[] 
        ? `${P}${K & string}` 
        : AllPaths<T[K], `${P}${K & string}.`> extends infer O 
          ? `${O & string}` | `${P}${K & string}`
          : never 
        : `${P}${K & string}`
}[keyof T]

AllPaths is a type that utilizes recursion to map over all properties of T and handle different scenarios based on the type of each property.

In the next step, we need a type that can extract the correct type for a given path.

type PathToType<T, P extends string> = P extends keyof T
  ? T[P]
  : P extends `${infer L}.${infer R}` 
    ? L extends keyof T
      ? PathToType<T[L], R>
      : never
    : never 

type T0 = PathToType<IUser, "address.street">
// type T0 = string

This type also uses recursion to handle the different possible scenarios when extracting types based on paths.

Finally, both types are used within the Matcher type.

type Matcher<T> = Partial<{
    [K in AllPaths<T>]: PathToType<T, K & string>
}>

The Matcher type utilizes AllPaths to generate keys and PathToType to fetch the corresponding type for each key in the resulting object.

You can test the types in the TypeScript Playground 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 reason behind capitalizing Angular CLI class file imports?

After creating a basic class in Angular using the CLI starter, I encountered an issue when trying to use the imported class. Instead of functioning as expected, it returned an empty object. Through troubleshooting, I discovered that simply changing the fil ...

Converting an array of arguments into tuples within the range of <T extends Tuple> is denoted by [T, (...args: NonNullArray<T>) => any], where each tuple represents the argument of a

Let's start with a simple function that takes a tuple as its first argument and a function whose arguments are elements of the tuple that are not null as its second argument: let first: number | null | undefined; let last: number | null | undefined; l ...

Is there a way to define a type once and then use it for both a function and a component in React using TypeScript?

I have a types.ts file where I define the Redux action types, a Screen.tsx file for a component that uses the actions, and an Actions.ts file where the actions are defined. I want to find a way to declare the action type only once and then use it across bo ...

find all the possible combinations of elements from multiple arrays

I have a set of N arrays that contain objects with the same keys. arr[ {values:val1,names:someName},   {values:val2,names:otherName}, ] arr2[   {values:valx,names:someNamex}, {values:valy,names:otherNamey}, ] My goal is to combine all possible c ...

Transferring data to a different module

I'm currently working on an Angular program where I am taking a user's input of a zip code and sending it to a function that then calls an API to convert it into latitude and longitude coordinates. Here is a snippet of the code: home.component.h ...

Using a union type annotation when passing into knex will result in the return of an unspecified

Knex version: 2.5.1 Database + version: postgres15 When passing a union typescript definition into knex as a type annotation, it returns the type any. However, by using type assertion as UserRecord, we can obtain the correct UserRecord type. It is my un ...

When transmitting information to the server, the browser initiates four requests

I am encountering an issue with my React component. The problem arises when I try to retrieve the current geographic coordinates, as they are being fetched 4 times consecutively. This same glitch occurs when attempting to send the coordinates to the serv ...

Troubleshooting the issue with mocking the useTranslation function for i18n in JEST

Currently, I am facing an issue with my react component that utilizes translations from i18next. Despite trying to create tests for it using JEST, nothing seems to be getting translated. I attempted to mock the useTranslation function as shown below: cons ...

Traverse the elements of a BehaviorSubject named Layer_Template

I am currently facing an issue with displaying data from my BehaviorSubject. I have come across a way to iterate through a BehaviorSubject using asyncpipe which subscribes to the Observable SERVICE todo.service.ts @Injectable() export class TodoService ...

Combining TypeScript and ReactJS with RequireJS: A guide to importing react-dom in TypeScript

I am currently working with TypeScript and React.js. Within Visual Studio 2015, I have set the project properties to use AMD as the module system for TypeScript build, meaning I am utilizing requirejs for importing modules. Within my main.tsx file, I am r ...

Arranging the properties of an object following the reduction process

I am currently working on replicating the functionality of an Outlook mailbox by organizing a list of Outlook emails based on their conversation ID. However, I am facing the challenge of needing to sort my list twice - once to order the emails in each grou ...

Angular functions are self-contained and cannot access anything external to their own scope

I created a classic interceptor for Angular, and now I am working on an interceptor for Syncfusion requests. While I have found a solution, I am facing a problem - I am unable to call a method in my function instance. Here is the snippet of my source code: ...

Solving the issue of interconnected promises in Angular services

I am utilizing a DynamoDB service within my Angular project which returns a promise through a series of promises. This process involves retrieving a subId from Cognito and then passing that subId to a DynamoDB get query: async getUserObject(): Promise< ...

Using res.locals with TypeScript in Next.js and Express

In my express - next.js application, I am transferring some configuration from the server to the client using res.locals. My project is written in typescript and I am utilizing "@types/next": "^8.0.6". The issue at hand: typescript is throwing an error st ...

Incorporate an HTML span element with an onclick function bound in Angular framework

Is there a way to incorporate different icons by adding a span based on a flag, with an onclick event that triggers an internal function defined in the component ts? testfunc(){ console.log("it works") } flagToIcon(flag: boolean) { switch ( ...

Maximize the performance of displaying images

At the moment, I have a set of 6 graphics (0,1,2,3,4,5)... The arrangement of these graphics looks fantastic! However, I am facing an issue when a user only has 3 graphics, for example 0, 2, and 5. In this scenario, my graphics do not line up correctly. D ...

Using a click event to target the next div and apply a CSS class using Typescript

I am trying to create a Typescript function that will locate the next div and apply a CSS class to it. Here is what I have attempted: <ul> <li><a href="#" onclick="toggle()">Item 1</a></li> <div class="content hide ...

Accurately Showing Values of Observable Objects

Hey there, I'm diving deeper into Angular and working on my first project. I am fetching data from my database using a service method: getPost(postID: String) { this.postDocument = this.db.doc('posts/' + postID) return this.postDo ...

Using Angular, you can effortlessly inject elements into the editable div from any location on the page

Currently, I am working on developing an HTML interface that allows users to input text and send it as a notification to our mobile application. However, I am encountering challenges with the text and dynamically inserted elements using Angular 5; The te ...

Typescript does not produce unused members

Having an issue with the JS code that TypeScript compiler is generating. Here's an example class: // Class export class UserDTO { Id: number; FirstName: string; LastName: string; DateOfBirth: Date; getFullName(): string { ...