Instructions for creating a function that can receive an array of objects containing a particular data type for the value associated with the key K

Seeking guidance on how to define a specific signature for a function that accepts an array of objects and 3 column names as input:

function customFunction<T, K extends keyof T>(
  dataset: T[],
  propertyOne: K,
  propertyTwo: K,
  propertyThird: K
): {
  ...
}

The current setup serves well, but now I want to specify the type of data stored in the propertyTwo column. It should only allow string values.

This means each item in object type T should resemble this structure:

T {
  [propertyOne]: any
  [propertyTwo]: string
  [propertyThird]: any
  [x?: string]: any
}

For example:

const dataset = [{age: 23, name: 'josh', country: 'america'}, ...]
customFunction(dataset, 'age', 'name', 'country')

How can I achieve this?

I attempted the following approach:

function customFunction<T, K extends keyof T, C extends string>(
  dataset: T[],
  propertyOne: K,
  propertyTwo: K, // How do I incorporate C here?
  propertyThird: K
): {
  ...
}

Answer №1

If your requirements or intentions differ for the usage of propertyOne, propertyTwo, and

property<strike>Three</strike>Third
, it's advisable to assign individual generic type parameters to each. Otherwise, the compiler will default to inferring a single type argument as a union of the three literal types, making it challenging to associate propertyTwo. Here is a refactored version:

function myFunction<
  T,
  K1 extends keyof T,
  K2 extends keyof T,
  K3 extends keyof T
>(
  dataset: T[],
  propertyOne: K1,
  propertyTwo: K2,
  propertyThird: K3
) {
  
}

To enforce that the property type of T at key K2 must be string, you can utilize a recursive constraint on T by specifying that it should be assignable to Record<K2, string> (utilizing the Record<K, V> utility type representing a type with keys K and values V):

function myFunction<
  T extends Record<K2, string>, // add constraint
  K1 extends keyof T,
  K2 extends keyof T,
  K3 extends keyof T
>(
  dataset: T[],
  propertyOne: K1,
  propertyTwo: K2,
  propertyThird: K3
) {
  dataset.forEach(x => x[propertyTwo].toUpperCase()); // okay
}
const dataset = [{ age: 23, name: 'josh', country: 'america' }]

const okay = myFunction(dataset, 'age', 'name', 'country'); // okay
const bad = myFunction(dataset, 'name', 'age', 'country'); // error!
// ------------------> ~~~~~~~

This implementation functions as intended. The compiler recognizes that each element in dataset has a property valued as string at the key propertyTwo.

Callers will also receive an error if the argument passed for propertyTwo does not correspond to a string property within elements of dataset. Bravo!


The only drawback is that callers may prefer to see the error on 'age' rather than on dataset and would appreciate sensible IntelliSense suggestions displaying only key names corresponding to string properties. To achieve this, adjust the constraint on K2.

Firstly, create a utility type KeysMatching<T, V> which calculates the keys of T where properties are assignable to

V</code. Since there isn't a built-in utility or mechanism operating this way (there's a request at <a href="https://github.com/microsoft/TypeScript/issues/48992" rel="nofollow noreferrer">microsoft/TypeScript#48992</a> for a native version recognized by the compiler), it needs to be constructed. Here's one possible method:</p>
<pre><code>type KeysMatching<T extends object, V> = keyof {
  [K in keyof T as T[K] extends V ? K : never]: any
};

In this scenario, I'm employing key remapping in mapped types to map T to a new type containing solely those keys K where T[K] extends V. If T corresponds to

{age: number, name: string, country: string}
and V represents string, then the mapped type would be {name: string, country: string}. Subsequently, we retrieve its keys using the keyof operator, yielding
"name" | "country"
.

In place of K2 extends keyof T, substitute

K2 extends KeysMatching<T, string>
:

function myFunction<
  T extends Record<K2, string>,
  K1 extends keyof T,
  K2 extends KeysMatching<T, string>,
  K3 extends keyof T
>(
  dataset: T[],
  propertyOne: K1,
  propertyTwo: K2,
  propertyThird: K3
) {
  dataset.forEach(x => x[propertyTwo].toUpperCase());
}
const dataset = [{ age: 23, name: 'josh', country: 'america' }]

const okay = myFunction(dataset, 'age', 'name', 'country');
const bad = myFunction(dataset, 'name', 'age', 'country'); // error!
// -----------------------------------> ~~~~~

Now, the constraint is successfully enforced, and errors are triggered where desired!


You might find it redundant that T is constrained to Record<K2, string> and K2 is restricted to

KeysMatching<T, string></code (applied constraints are essentially identical). However, due to the compiler's inability to comprehend what <code>K2 extends KeysMatching<T, string>
implies within the myFunction() implementation... Therefore, removing the
T extends Record<K2, string>
constraint yields:

function myFunction<
  T,
  K1 extends keyof T,
  K2 extends KeysMatching<T, string>,
  K3 extends keyof T
>(
  dataset: T[],
  propertyOne: K1,
  propertyTwo: K2,
  propertyThird: K3
) {
  dataset.forEach(x => x[propertyTwo].toUpperCase()); // error!
  // -------------------------------> ~~~~~~~~~~~
  // Property 'toUpperCase' does not exist on type 'T[K2]'
} 

This underscores the existence of microsoft/TypeScript#48992; if a native KeysMatching were present, theoretically, just

K2 extends KeysMatching<T, string></code could be written, enabling the compiler to understand that <code>T[K2]
ought to be assignable to string. Nevertheless, since such functionality doesn't currently exist, maintaining the redundant constraint proves beneficial.

Playground link to code

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

Having difficulty reaching the specified route ID in Angular app

I'm encountering an issue when attempting to navigate to a route with an ID argument using the router. Here's the code snippet from my component: import { Router } from '@angular/router'; ... constructor(private router: Router) { } .. ...

What is the best way to sort an array of objects based on their properties?

I am working with an array of objects: let response = [{"id": 1, "name": "Alise", "price": 400, "category": 4}]; In addition to the array of objects, I have some arrays that will be used for filtering: let names = ["Jessy", "Megan"]; let prices = [300, ...

Did not adhere to regulations

Currently, I am in the process of developing a React app. However, when I attempt to initiate my project using npm start in the terminal, I encounter an error message on the browser. https://i.stack.imgur.com/wej1W.jpg Furthermore, I also receive an erro ...

Unsynchronized state of affairs in the context of Angular navigation

Within my Angular project, I am currently relying on an asynchronous function called foo(): Promise<boolean>. Depending on the result of this function, I need to decide whether to display component Foo or Bar. Considering my specific need, what woul ...

Tips for utilizing an elective conversion by typing

In my opinion, utilizing code is the most effective approach to articulate my intentions: interface Input { in: string } interface Output { out: string } function doStuff(input: Input): Output { return { out: input.in }; } function f<Out>(input ...

Extremely sluggish change identification in combination Angular application

We are encountering consistent issues with slow change detection in our hybrid AngularJS / Angular 8 app, especially when dealing with components from different versions of the framework. The problem seems to arise when using older AngularJS components wit ...

The Cypress-TinyMCE package consistently returns undefined for the editor instance when using TypeScript

My current project involves building a React JS application with TypeScript, where I utilize the TinyMCE editor within a form. To further enhance my development process, I am incorporating integration tests using Cypress in TypeScript. However, I have enco ...

The type 'MockStoreEnhanced<unknown, {}>' is not compatible with the type 'IntrinsicAttributes & Pick[...]

I'm currently working on creating a unit test for a container in TypeScript. From what I've gathered from various responses, it's recommended to use a mock store and pass it to the container using the store attribute. This method seems to o ...

The test session failed to launch due to an error in initializing the "@wdio/cucumber-framework" module. Error message: [ERR_PACKAGE_PATH_NOT_EXPORTED]

I added @wdio/cli to my project using the command 'npm i --save-dev @wdio\cli'. Next, I ran 'npx wdio init' and chose 'cucumber', 'selenium-standalone-service', 'typescript', 'allure' along w ...

Using regular expressions, you can eliminate a specific segment of a string and substitute

Provide a string in the following format: lastname/firstname/_/country/postalCode/_/regionId/city/addressFirst/addressSecond/_/phone I am creating a function that will extract the specified address parts and remove any extra parts while maintaining maxim ...

Combining numerous interfaces into a unified interface in Typescript

I'm struggling to comprehend interfaces in Typescript, as I am facing difficulty in getting them to function according to my requirements. interface RequestData { [key: string]: number | string | File; } function makeRequest(data: RequestData) { ...

amChartv5 on Angular is successfully displaying a Gantt chart that includes multiple categories with the same name being resolved

I've been working on integrating an amCharts v5 gantt chart with Angular 13. Each bar in the chart represents a project, and if there are multiple occurrences of a category, they should stack like a timeline. I've successfully retrieved data from ...

Tips for troubleshooting TypeScript Express application in Visual Studio Code

Recently, I attempted to troubleshoot the TypeScript Express App located at https://github.com/schul-cloud/node-notification-service/ using Visual Studio Code. Within the launch.json file, I included the following configuration: { "name": "notifi ...

Can getters and setters be excluded from code coverage reports in Angular projects?

Looking to clean up my coverage reports for the front end portion of an angular project by removing trivial code like getters and setters. I generate my reports using npm run test-sonar -- --coverage, but everything is included in the report when I view ...

Dealing with implicit `any` when looping through keys of nested objects

Here is a simplified example of the issue I am facing: const testCase = {a:{b:"result"}} for (const i in testCase) { console.log("i", i) for (const j in testCase[i]){ console.log("j", j) } } Encountering ...

There seems to be an issue with the authorization function in nextauthjs utilizing TypeScript

In my NextJS application utilizing nextAuth with TypeScript, I am encountering difficulties implementing the credentials provider. Below is a snippet from my api\auth\[...nextauth]\route.ts file: CredentialsProvider({ name: 'cre ...

ng2-toastr in conjunction with akveo/ng2-admin - Styles not being applied

I recently integrated ng2-toastr into my akveo/ng2-admin dashboard, utilizing the latest version with Angular 4. Following the provided installation documentation, I imported the necessary CSS in the index.html file and declared ToastModule in the app.modu ...

Error in Typescript: "Cannot assign to parameter that is of type 'never'"

Here is the code snippet that I am working with: FilesToBlock: []; //defined in this class //within a method of the class this.FilesToBlock = []; this.FilesToBlock.push({file: blockedFile, id: fileID}); However, I'm encountering an issue with fil ...

Expanding the functionality of an external module using d.ts in a TypeScript project

Currently, I am in the process of developing a nodejs application using typescript with multiple external libraries such as express.js. Like many other libraries, express is also designed to be extendable. I am looking to enhance it by including a custom ...

Utilizing AWS Websockets with lambda triggers to bypass incoming messages and instead resend the most recent message received

I am facing an issue when invoking a lambda that sends data to clients through the websocket API. Instead of sending the actual message or payload, it only sends the last received message. For example: Lambda 1 triggers Lambda 2 with the payload "test1" ...