Is there a way to ensure in TypeScript that a generic type includes a property that implements a particular method?

To better explain my query, let me provide an illustration.

Suppose I aim to create a method that accepts three parameters, as shown below:

customFilter<T>(values: T[], filterString: string, propName: string) any[] {
    return values.filter((value) => value[propName].includes(filterString));
}

In this scenario, I want to ensure that T is a type that includes a property propName that is a string (or any type that has an includes method).

I have contemplated defining an interface for the first parameter's type, but it seems to reflect my C# background. Using an interface would necessitate hardcoding the property name, which I believe is not the optimal solution.

I am aware that keyof plays a role in this context.

To address this concern, I have implemented a quick validation in the method:

typeof(values[propName].includes) === 'function'

However, this approach feels overly reliant on JavaScript, and I have a hunch that TypeScript offers a more suitable alternative.

I understand that adopting a method similar to this response could resolve the issue, but it retains a strong JavaScript element.

Answer №1

Upon dissecting the code example you provided, it is evident that you aim to filter the values parameter against a filterString while applying it to a specified propName that must be present in a generic T type, but with the condition that the value is an array.

There are a few key points that we need to address:

i) It is crucial to restrict T to have a property that extends an Array. This poses a challenge because TypeScript lacks a direct way to define an interface with at least one property of a specific type. However, we can implement a workaround to indirectly enforce this constraint.

ii) Moving on to the arguments, we need to ensure that the filterString extends the generic type of the array being filtered. For example, if you intend to filter a propName whose value is an Array<string>, the filterString argument should be of type string.

iii) By constraining T, we then define the argument propName to be keys of T with values that are arrays, in this case, of type string.

While the implementation of your function definition remains the same, we could rearrange the arguments for the sake of clarity. Let's begin coding:

Firstly, we create a type that can extract the properties from T that match a specific type. You can refer to @jcalz's solution for defining such an interface.

type Match<T, V> = { [K in keyof T]-?: T[K] extends V ? K : never }[keyof T];

Now, we define the function based on the aforementioned steps:

// Defining types to filter keys with array values
type HasIncludes<T> = Match<T, { includes: (x: any) => boolean }>;
// Extracting the generic type of the array value
type IncludeType<T, K extends keyof T> =  T[K] extends { includes: (value: infer U) => boolean } ? U : never;

// Applying the types to the function arguments
function customFilter<T, K extends HasIncludes<T>>(values: T[], propName: K, filterString: IncludeType<T, K>) {
    // Casting to any as a workaround for TypeScript inference issue

    return values.filter((value) => (value[propName] as any)
      .includes(filterString))
}

The function is defined, and now we proceed to set up some test cases:

const a = [{ age: 4, surname: 'something' }];
const b = [{
    person: [{ name: 'hello' }, { name: 'world' }]
}];

// In this scenario, there is an indirect error as any referred key does not meet the argument constraint
const c = [{ foo: 3 }]

// The generic type is implicitly inferred by its usage
const result = customFilter(b, 'person', { name: 'type' }); // Works as expected
const result = customFilter(a, 'age', 2); // TypeScript error due to 'age' not being an array

Feel free to experiment with the playground for further testing and exploration.

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

Transferring data between components in Ionic 2: Service to Page

My service code includes two functions - one for creating a native storage with IP and port, and the other for retrieving values from the native storage. DatabaseService export class DatabaseService { ... public ip: string; public port: string; . ...

Angular 5 error: The property you are trying to access is not defined in

I've been working on a simple class and I'm stuck trying to figure out why the "currentHost" variable is showing as non-existent. export class AppModule { public static currentHost: string = 'http://localhost:8080/'; constructor() ...

Determining the instance type of a TypeScript singleton class

I have a unique singleton implementation: class UniqueSingleton { private static instance: UniqueSingleton; private constructor() { // Only allows instantiation within the class } public static getInstance(): UniqueSingleton { if (!Unique ...

Is it possible to encounter an unusual token export while trying to deactivate Vue with veevalidate

Utilizing Nuxt with server side rendering. Incorporating Typescript along with vee-validate version 3.4.9. The following code has been validated successfully extend('positive', value => { return value >= 0; }); Upon adding the default, ...

Unable to retrieve a boolean with the statement "return of(false)"

My objective is to retrieve data, store it, and return either true or false based on the operation outcome. Initially, I attempted to make the call and then use return of(true) to create an observable. The method I have is as follows. setValidations(): Ob ...

Troubleshooting CORS errors in axios requests within a Next.js application

Encountering an issue while attempting to make an axios call to my API on a different localhost. How can this be resolved? The tech stack being used includes Next.js, TypeScript, and Axios. Below is the function which - although written poorly for testing ...

Using jest in typescript to simulate HttpRequest body and InvocationContext in @azure/functions

I have the following function and I am trying to write a test for it, but I'm having trouble figuring out how to mock HttpRequest import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions"; export async function ...

Oh no! A critical mistake has occurred: Mark-compact operations are not working efficiently near the heap limit, leading to a failed allocation due to the

My project is not particularly complex, I only just started it. However, when I run the command npm run dev, node js consumes more than 4GB of memory and eventually crashes with a fatal error: --- Last few GCs --- [16804:0000018EB02350F0] 128835 ms: Mar ...

What is the best way to set the typing of a parent class to the child constructor?

I am seeking a method to inherit the parameter types of a parent's constructor into the child's constructor. For example: class B extends A { constructor (input) { super(input); } } I attempted the following: class B extends ...

Is there a way to deactivate the spin buttons for an input number field?

Is there a way to create an input element with type number in Vue using createElement() in TypeScript and then disable the spin buttons for increment and decrement? I attempted to use the following CSS: input[type=number]::-webkit-inner-spin-button, input ...

Objects vanish 10 seconds after appearing [Angular2, *ngFor]

My Angular2 template is quite straightforward: <span *ngFor="let item of items"> {{ item.description }} </span> Here is the TypeScript logic for it: let list = new Map(); for(let j = 0; j < 100; j++) { list.set(j, { description: j.toS ...

Converting a dynamic JSON object into a generic type in TypeScript

I need assistance with converting a JSON object into the equivalent generic type in TypeScript. The JSON object I have contains dynamic keys such as applications and permissions. The keys inside applications, like application_management and user_managemen ...

There seems to be a console error in Angular 5 when using IE 11

I've developed an Angular 4 application using MVC and Visual Studio 2015. Everything runs smoothly when I access the application on Chrome, but I encounter the following exception on IE 11: XHR error: (404 Not Found) loading http://localhost/src/ma ...

What is the best way to transmit two distinct sets of data from a child component to the v-model of a parent component?

Currently, I am working on a project using vuejs 2 and typescript. In this project, I need to pass two different sets of data - data and attachments - within the parent component. I am utilizing vue-property-decorator for this purpose. However, I am facing ...

Is it possible to deduce the output type of a function based on its input?

In a web development project, the function getFormData() plays a crucial role in validating and sanitising a FormData object based on a specified schema. If the validation process goes smoothly without any errors, the function will return the cleansed Form ...

TS - decorator relies on another irrespective of their position within the class

Is it possible to consistently run function decorator @A before @B, regardless of their position within the class? class Example { @A() public method1(): void { ... } @B() public method2(): void { ... } @A() public method3(): void { ... } } In the sc ...

AutoAnimate animation customization is not compatible with React

I'm in the process of integrating a plugin from this source into my code. I've made adjustments to it for React, but it's still not working as expected. Can you assist me with resolving this issue? Custom Hook: import { useRef, useLayoutEff ...

Issues arise when attempting to override attributes within the HTML of a parent component in Angular

Why does overriding an attribute in a child class that extends from another not work as expected? Here's a made-up scenario to simplify the issue: Parent class file: gridbase.component.ts import { Component, OnInit } from '@angular/core'; ...

What is the best way to populate an Angular variable in Ionic following a subscription?

Currently, I am in the process of retrieving data from a server and displaying it on an Ionic page. I have successfully fetched the data without any issues and verified it in the console. However, how do I proceed once the server returns the data to me? T ...

What could be causing an error with NextJS's getStaticPaths when running next build?

When attempting to use Next.js's SSG with getStaticPaths and getStaticProps, everything worked fine in development. However, upon running the build command, an error was thrown: A required parameter (id) was not provided as a string in getStaticPath ...