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

Whenever I try to execute 'docker build --no-cache -t chat-server .', I always encounter type errors

Below is the Dockerfile located in the root directory of my express server: FROM node:18 WORKDIR /usr/src/server COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 RUN npm run build CMD ["npm", "start"] Here is the contents of my .dockerign ...

Using TypeScript with React Bootstrap's <Col> component and setting the align attribute to 'center' can trigger a TS2322 warning

The React app I'm working on includes the code below. The Col component is imported from React-bootstrap <Col md={5} align="center"> This is a column </Col> When using Typescript, I received the following warning: ...

Updating the main window in Angular after the closure of a popup window

Is it possible in Angular typescript to detect the close event of a popup window and then refresh the parent window? I attempted to achieve this by including the following script in the component that will be loaded onto the popup window, but unfortunatel ...

What could be causing my NextJS application to not recognize the _document.tsx file?

Seeking assistance in understanding why my _document.tsx is not loading properly within my nextJS application. My Attempts So Far I have been diligently following the NextJS documentation for creating a custom _document.js. Despite my efforts, I am unable ...

Error TS2339: The 'phoneType' property cannot be found on the 'Object' data type

Below is the declaration of an object: export class Card { private _phones:Object[] get phones(): Object[]{ if(this._phones === undefined) this._phones = [] return this._phones } set phones(val:Object[]){ ...

Set the enumeration value to a variable

I am facing a problem where it seems impossible to do this, and I need help with finding a solution enum Vehicles { BMW='BMW', TOYOTA='Toyota' } class MyVehicles { public vehicleName: typeof Vehicles =Vehicles; } const veh ...

Exploring the Differences Between ionViewWillEnter and ionViewDidEnter

When considering whether to reinitiate a cached task, the choice between ionDidLoad is clear. However, when we need to perform a task every time a view is entered, deciding between ionViewWillEnter and ionViewDidEnter can be challenging. No specific guid ...

What are the steps to connecting incoming data to an Angular view utilizing a reactive form?

Hello, I am currently fetching data from an API and have successfully displayed the teacher values. However, I am unsure of how to utilize the incoming array values for "COURSES" in my Angular view. This is the response from the REST API: { "courses ...

Enrich your TypeScript code by unleashing the power of enum typing in overloading logical

I have a custom enum called PathDirection that represents different directions export enum PathDirection { LEFT="LEFT"; RIGHT="RIGHT"; }; Within my code, I need to toggle between the two directions. For example: let currentDire ...

Merging declarations fails to function properly following the release of the npm module

The file core.ts contains the definition of a class called AnyId. In another file named time.ts, more methods are added to the AnyId class. This is achieved by extending the type of AnyId using declaration merging: declare module './core' { in ...

Incorporating the unshift method in JavaScript: A Step-by-

I'm looking to create a new function with the following requirements: Function add(arr,...newVal){ } array = [1,2,3]; add(array,0) console.log(array); //I want this to output [0,1,2,3] I tried creating the function similar to push like this: ...

Identifying when an element is in or out of view using Next.js and TypeScript

I'm currently working on an app using Next and Typescript. The app features a navigation bar at the top of the screen, and I need it to change its style once it reaches a certain point in the view. I attempted to use jQuery for this purpose, but encou ...

I'm looking to find the Angular version of "event.target.value" - can you help me out?

https://stackblitz.com/edit/angular-ivy-s2ujmr?file=src/app/pages/home/home.component.html I am currently working on getting the dropdown menu to function properly for filtering the flags displayed below it. My initial thought was to replicate the search ...

Nest faces difficulty resolving the dependencies required by the TMPController

I've tried everything to fix this error, but nothing seems to be working. tmp.module.ts import { Module } from "@nestjs/common"; import { TMPController } from "./tmp.controller"; import { TMPService } from "./tmp.service"; @Module({ controllers: ...

Alter the class based on the incoming string from the rxjs stream

I have a stream that outputs strings, and based on these strings I want to apply certain classes to a specific tag: If the string is "ok", add class "fa-check" If the string is "loading", add classes "fa-spin" and "fa-spinner" If the string is "error", a ...

The CloudWatch logs for a JavaScript Lambda function reveal that its handler is failing to load functions that are defined in external

Hello there, AWS Lambda (JavaScript/TypeScript) is here. I have developed a Lambda handler that performs certain functions when invoked. Let me walk you through the details: import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda' ...

What is the best way to play AudioBuffer on an iPhone device?

When retrieving audio data from a stream, I encounter an issue with playing audio on iPhone Safari. The sound does not play unless I allow mediaDevice access for audio. Is there a way to play the audio without having to grant this permission? This is the ...

Is it feasible to access a service instance within a parameter decorator in nest.js?

I am looking to replicate the functionality of Spring framework in nest.js with a similar code snippet like this: @Controller('/test') class TestController { @Get() get(@Principal() principal: Principal) { } } After spending countless ho ...

You can only use a parameter initializer within the implementation of a function or constructor

I recently started learning TypeScript and am currently using it for React Bricks. I've been working on rendering a 3D object with three.js, but I keep encountering the error mentioned above. I've attempted various solutions such as passing color ...

Storing application state using rxjs observables in an Angular application

I'm looking to implement user status storage in an Angular service. Here is the code snippet I currently have: import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @Injectable() expo ...