How can a secondary property type be determined by the key utilized in another property?

My goal is to develop a filter type that uses the primary object type to specify a set of keys for "field" and then assigns the appropriate type to the "value". However, I have encountered challenges in achieving this as the best outcome I could attain was a union of possible value types. Most of the available documentation focuses on functions which work perfectly well; unfortunately, I am unsure how to extend a type across multiple properties in this scenario. In addition, I am interested in exploring whether it is possible to restrict the "operator" union to include items like "startswith" only if the value type is a string. Any suggestions or resources on this matter would be greatly appreciated. Below is an example code snippet:

type FilterItem<T, K extends keyof T> = {
    field: K;
    value: T[K];
    operator: 'eq'|'neq'|'startswith'|'doesnotstartwith';
}
type Test = {
    name: string;
    age: number;
}
const filter = {
    field: 'age',
    value: 'test', //should error as "age" is a number
    operator: 'startswith' //should error as "age" is not a string
} as FilterItem;

Answer №1

When structuring your code, it is essential to ensure that the filter variable aligns with a union type that includes all properties of the Test object. Here's an example:

type FilterItemTest = {
    field: "name";
    value: string;
    operator: "startswith" | "doesnotstartwith" | "eq" | "neq";
} | {
    field: "age";
    value: number;
    operator: "eq" | "neq";
}

By defining the types in this way, you can expect the desired behavior and receive errors for any incorrect assignments. For more information on potential issues, refer to microsoft/TypeScript#39438 and microsoft/TypeScript#40934.

let filter: FilterItemTest;
filter = { field: 'age', value: 10, operator: "eq" } // valid
filter = { field: 'age', value: 'test', operator: 'startswith' } // error, incompatible value
filter = { field: 'age', value: 10, operator: 'startswith' } // error, incompatible operator
filter = { field: 'name', value: 'test', operator: 'startswith' } // valid

In order for FilterItem<T> to be versatile across different objects like Test, incorporating generics is crucial. Here's one approach to achieve this:

To handle acceptable operators based on the property type, define Operator<T>:

type Operator<T> =
    (T extends string ? 'startswith' | 'doesnotstartwith' : never)
    | 'eq' | 'neq'

This conditional type ensures the correct operators are assigned depending on the data type. Test this functionality as follows:

type OperatorString = Operator<string>;
// Result: "startswith" | "doesnotstartwith" | "eq" | "neq"
type OperatorNumber = Operator<number>;
// Result: "eq" | "neq"

Now, define FilterItem<T>:

type FilterItem<T> = { [K in keyof T]-?: {
    field: K;
    value: T[K];
    operator: Operator<T[K]>
} }[keyof T]

This distributive object type enables mapping over the properties of T to generate union members effectively. It distributes the operation across all keys in keyof T. Verify its correctness here:

type Test = {
    name: string;
    age: number;
}

type FilterItemTest = FilterItem<Test>;
/* Result:
{
  field: "name";
  value: string;
  operator: Operator<string>;
} | {
  field: "age";
  value: number;
  operator: Operator<number>;
}
*/

The dynamically created type FilterItem<Test> mirrors the manual creation of FilterItemTest, validating the successful completion of our task.

Explore code in TypeScript 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 combination of Stripe, Angular, and TypeScript is not compatible

Attempting to utilize Stripe.card.createToken() in order to generate a token for backend usage has proven to be challenging. Integrating this functionality with Angular and TypeScript requires careful coordination. Currently, the angular-stripe and stripe. ...

Typescript error: Import statement not allowed here

Recently delving into the world of TypeScript, I encountered an issue when attempting to build for production. My first step was running tsc Although this step passed without any errors, I faced import errors when trying to execute the build file with ...

Retrieving the property of a union type comprising a void type and an unnamed type

Currently, I am working on a project involving GraphQL. In my code, I have encountered a GraphQLError object with a property named extensions. The type of this property is either void or { [key: string]: any; }. Whenever I try to access any property within ...

Communicating Progress Updates from C# to Angular 6 Using HttpPost

I'm building an Angular 6 application with a progress bar that displays the rendering and downloading progress of a PDF file as a percentage. Here's my Post call: renderReport(renderObjectId: number): Observable<HttpEvent<Blob>> { ...

Nextjs build: The specified property is not found in the type 'PrismaClient'

I have a NextJS app (TypeScript) using Prisma on Netlify. Recently, I introduced a new model named Trade in the Prisma schema file: generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url ...

"NameService is not provided in Angular, please check your module

I've been struggling with loading a class into my Angular component. I've spent quite some time trying to figure out the solution, even attempting to consolidate everything into a single file. Here is what I have: Application.ts /// <referenc ...

React with Typescript: It appears that you are attempting to utilize Typescript without having it properly installed on your system

I am embarking on creating a React application integrated with TypeScript. Initially, I visited the React website to seek guidance on incorporating TypeScript in my project. The website directed me to execute the following command in the terminal: npx crea ...

How to redefine TypeScript module export definitions

I recently installed a plugin that comes with type definitions. declare module 'autobind-decorator' { const autobind: ClassDecorator & MethodDecorator; export default autobind; } However, I realized that the type definition was incorrec ...

What is the process for importing JSON from an NPM package in Angular version 15?

I've been dealing with a local package that contains a json file, and my current challenge is to load this json file into the Angular 15 app.component.ts. To bring the json file package into my Angular project, I followed this installation process: n ...

Transform Promise-based code to use async/await

I'm attempting to rephrase this code using the async \ await syntax: public loadData(id: string): void { this.loadDataAsync() .then((data: any): void => { // Perform actions with data }) .catch((ex): v ...

Is the autoIncrement property missing from the IDBObjectStore Interface in Typescript 1.8 lib.d.ts file?

Upon examining the specifications on various pages, it is evident that there is a specified read-only property named "autoIncrement" within the IDBObjectStore: https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore https://developer.mozilla.org/ ...

Tips for customizing a generic repository error message

I have a GenericRepository class that provides basic functionality for interacting with persistence storage such as creating, finding, getting all, deleting, and updating data. Within the find method, I am searching the database using its primary key. If ...

What is the best approach to organize data from an observable based on a nested key?

I'm currently developing a new application and struggling with grouping data. The data is being pulled from an observable, and I need to group objects by their status and push them into an array. I attempted to use the groupBy() method, but unfortunat ...

How can we use Angular Table to automatically shift focus to the next row after we input a value in the last cell of the current row and press the Enter key

When the last cell of the first row is completed, the focus should move to the next row if there are no more cells in the current row. <!-- HTML file--> <tbody> <tr *ngFor="let row of rows;let i=index;" [c ...

"Encountering a Prisma type error while attempting to add a new record

I am currently utilizing Prisma with a MySQL database. Whenever I attempt to create a new record (School), an error of type pops up in the console. Additionally, I am implementing a framework called Remix.run, although it does not seem to be causing the is ...

The callback function inside the .then block of a Promise.all never gets

I'm currently attempting to utilize Promise.all and map in place of the forEach loop to make the task asynchronous. All promises within the Promise.all array are executed and resolved. Here is the code snippet: loadDistances() { //return new Prom ...

A static method written in Typescript within an abstract class for generating a new instance of the class itself

Imagine I have abstract class Foo { } class Bar1 extends Foo { constructor(someVar) { ... } } class Bar2 extends Foo { constructor(someVar) { ... } } I want to create a static method that generates an instance of the final class (all construct ...

What is the best way to emphasize when the path matches exactly with '/'?

Is there a way to highlight the path only when it exactly matches '/'? Currently, even on 'Page 2', the 'Home' link is still highlighted. Check out the plunker here .active { color: red; } <a routerLinkActive="active" r ...

Converting React to Typescript and refactoring it leads to an issue where the property 'readOnly' is not recognized on the type 'IntrinsicAttributes & InputProps & { children?: ReactNode; }'

I'm currently in the process of refactoring an application using Typescript. Everything is going smoothly except for one particular component. I am utilizing the Input component from the library material-ui. import {Input} from "material-ui"; class ...

Clicking on the <Link to=URL> in a React application built with Typescript and Redux triggers the disappearance of the component

Issue Background The application was created using npx create-react-app rrts --typescript, which sets up React, Redux, and Typescript. Problem Visualization (Content is the component with sentences) View Problem Image Here Problem Description Clicking o ...