determining the data type based on the function parameter even when a specific type parameter is provided

Consider this example:

type UpdateFieldValue<T extends Record<string, unknown>> = (key: keyof T, value: SomeType) => void

The goal is to have SomeType represent the value type of the property (key) within object T, with key being provided through the first function parameter.

For instance, if T is defined as:

{ age: number, name: string }

Then for:

UpdateFieldValue<{ age: number, name: string }>('name'...

TypeScript would expect a string as the second parameter, and likewise for:

UpdateFieldValue<{ age: number, name: string }>('age'...

A number should be provided as the second argument in this case.

Despite initially making progress, encountering issues when explicitly passing T as a type parameter has proven challenging. Any suggestions are greatly appreciated.

Answer №1

When you make the resulting function generic, a relationship between the two arguments can be established based on the type T:

type UpdateFieldValue<T extends Record<string, unknown>> = <K extends keyof T>(key: K, value: T[K]) => void

declare const foo: UpdateFieldValue<{ age: number, name: string }>

// Works
foo('age', 3);

// Not valid
foo('name', 3);

TypeScript Playground

This method works well when the type of your first argument is already narrowed to a literal type, allowing TypeScript to infer the generic K and subsequently T[K].

However, calling it with a union as the generic type, either explicitly or through inference, might pose challenges:

type UpdateFieldValue<T extends Record<string, unknown>> = <K extends keyof T>(key: K, value: T[K]) => void

declare const foo: UpdateFieldValue<{ age: number, name: string }>

// Valid
foo<'age' | 'name'>('age', 'string');

const bar = Math.random() > 0.5 ? 'age' : 'name';

// Also valid
foo(bar, 3);

TypeScript Playground

If this poses an issue for you, there's an alternative solution that restricts only string literal types. Although more complex, it enforces the restriction using utility types:

It utilizes an Equals type and a UnionToIntersection type to ensure only literal types are allowed. This approach may not work seamlessly with certain types like Record<string, boolean>, but covers many scenarios.

Here's how this implementation could be implemented:

/**
 * @see {@link https://github.com/microsoft/TypeScript/issues/27024#issuecomment-421529650}
 */
type Equals<A, B> = (
    <T>() => T extends A ? 1 : 2
) extends (
    <T>() => T extends B ? 1 : 2
) ? true : false;

/**
 * @see {@link https://stackoverflow.com/a/50375286/1710523}
 */
type UnionToIntersection<U> = (
    U extends unknown ? (arg: U) => void : never
) extends (arg: infer I) => void ? I : never;

type LiteralOnly<S> = Equals<S, UnionToIntersection<S>> extends true ? S : never;

type UpdateFieldValue<T extends { [key: string]: unknown; }> = <K extends keyof T>(key: LiteralOnly<K>, value: T[K]) => void;

declare const foo: UpdateFieldValue<{ age: number, name: string }>;

// Invalid
foo<'age' | 'name'>('age', 'string');
foo('age', 'string');

// Valid
foo('age', 3);
foo('name', 'string');

const uncertainKey = Math.random() > 0.5 ? 'age' : 'name';

// Invalid
foo(uncertainKey, 3);

declare const bar: UpdateFieldValue<Record<string, boolean>>;

// Still not allowed, but perhaps should be
bar(uncertainKey, true);

// Valid
bar('foo', true);

TypeScript Playground


An alternative approach that overcomes some limitations of the generics solution involves linking the arguments' types by defining them under a single type. This is done using spread syntax along with a tuple type.

The tuple type can be created as a discriminated union by utilizing an immediately indexed mapped type, which allows for narrowing both arguments' types within the function by narrowing one of them.

Although the downside includes less friendly intellisense and unclear error messages when invalid arguments are passed, this method provides better results in terms of validity checking:

type UpdateFieldValue<T extends Record<string, unknown>> = (
    ...[key, value]: { [K in keyof T]: [K, T[K]] }[keyof T]
) => void

declare const foo: UpdateFieldValue<{ age: number, name: string }>

// Invalid
foo<'age' | 'name'>('age', 'string');
foo('age', 'string');

// Valid
foo('age', 3);
foo('name', 'string');

const uncertainKey = Math.random() > 0.5 ? 'age' : 'name';

// Invalid
foo(uncertainKey, 3);

declare const bar: UpdateFieldValue<Record<string, boolean>>;

// Allowed
bar(uncertainKey, true);

// Valid
bar('foo', true);

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

Unable to make custom font work in TailwindCSS and ReactJS project

I have incorporated a custom font into my projects using React, TypeScript, TailWind, and NextJS. The font file is stored in the /fonts directory with the name Glimer-Regular.ttf. To implement the font, I added the following code snippet to my global.css ...

Issue with ion-select default value not being applied

In my ion-select element, I have multiple options and I want to set a default value based on the CurrentNumber when the view is loaded. Here's the code snippet: <ion-select formControlName="Level"> <ion-option [value]="level.id" *n ...

Sort your list efficiently with a custom hook in React using Typescript

I've been working on developing a custom hook in React that sorts an array based on two arguments: the list itself and a string representing the key to sort by. Despite trying various approaches, I haven't been able to find a solution yet. I&apos ...

Tips on deactivating a div when a checkbox is selected

I am currently working with a checkbox element in my code: <md-checkbox checked.bind="addEventCommand.allDay" change.delegate="allday()">All Day</md-checkbox> When the above checkbox is true, I want to disable the following ...

What is the most effective method for identifying duplicate values in a multidimensional array using typescript or javascript?

I have a 2D array as shown below: array = [ [ 1, 1 ], [ 1, 2 ], [ 1, 1 ], [ 2, 3 ] ] I am looking to compare the values in the array indexes to check for duplicates. For example array[0] = [1,1]; array[1] = [1,2]; array[2] = [1,1]; We can see that ...

What is the process for setting the values of an object within a constructor to all class properties?

I am attempting to easily transfer all the properties from an object in a constructor to a class's properties type tCustomUpload = { name : string, relationship : string, priority : number, id : number } class CustomUpload { name : ...

Angular2: Going back a couple of steps

Is there a method to go back two steps when clicking on (click) goBack($event) instead of using just this.location.back()? I am looking for a way to access a list of locations in an array and cut out the last element. ...

Switch up the default font in your Nuxt 3 and Vuetify 3 project

I've been doing a lot of searching on Google, but I can't seem to find the solution. It seems like the challenge might be that the Nuxt 3 + Vuetify 3 combination isn't widely used yet? My current task is to implement a custom default font. ...

The service being injected is not defined

Two services are involved in this scenario, with the first service being injected into the second service like so: rule.service.ts @Injectable() export class RuleService { constructor( private _resourceService: ResourceService ){} s ...

How can I postpone the initialization of ngOnInit in Angular 7?

While attempting to send and retrieve data for display, I encountered an issue where the component initializes before the new data is added to the server. This results in a missing element being displayed. Is there a way to delay the initialization proce ...

Convert YAML to an array of objects instead of using named objects in npm parsing

Currently, I am utilizing npm's YAML parser to convert YAML into an object. However, instead of getting an array, I am receiving a group of named objects. This issue arises from the absence of dashes preceding the objects. How can I transform this gr ...

What is the best way to open and view files in an NPM dependency that do not use JavaScript?

I am facing an issue with an NPM project setup where my-config is a dependency of my-api. In the my-config project, there is a line of code that fetches the aws-config.ini file from the etc folder: instance.configs.aws = ini.parse(fs.readFileSync('./ ...

What could be the reason for Typescript attempting to interpret files in the `./build` directory as input files?

After struggling for an hour on this issue, I am stuck. The variables outDir and rootDir are set. However, the problem arises when only src is included in include, TypeScript shows the configuration via showConfig, yet it's attempting to compile 4 fi ...

Struggled with the implementation of a customized Angular filter pipe

I have recently developed a custom filter type to sort the notes-list in my application, with each note containing a 'title' and 'message'. Although there are no errors, I am facing issues as the pipe doesn't seem to be working pr ...

Harness the power of the Node.js Path module in conjunction with Angular 6

I'm currently facing an issue with utilizing the Path module in my Angular 6 project. After some research, I came across a helpful post detailing a potential solution: https://gist.github.com/niespodd/1fa82da6f8c901d1c33d2fcbb762947d The remedy inv ...

Transform a specialized function into a generic function with static typing

First off, I have a network of routes structured like this: interface RouteObject { id: string; path: string; children?: RouteObject[]; } const routeObjects: RouteObject[] = [ { id: 'root', path: '/', children: [ ...

Issue encountered while generating a dynamic listing using Angular

My goal is to generate a dynamic table using Angular. The idea is to create a function where the user inputs the number of rows and columns, and based on those values, a table will be created with the specified rows and columns. However, I am facing an iss ...

Exploring deeply nested arrays of objects until a specific condition is satisfied

My array is structured in a nested format as shown below. const tree = { "id": 1, "name": "mainOrgName", "children": [ { "id": 10, "name": "East Region", "children": [ ...

Error: Unable to access 'nativeElement' property from undefined object when trying to read HTML element in Angular with Jasmine testing

There is a failure in the below case, while the same scenario passes in another location. it('login labels', () => { const terms = fixture.nativeElement as HTMLElement; expect(terms.querySelector('#LoginUsernameLabel')?.tex ...

Prisma designs a personalized function within the schema

In my mongo collection, there is a field named amount. My requirement is to have the amount automatically divided by 100 whenever it is requested. In Django, this can be achieved with a custom function within the model. Here's how I implemented it: cl ...