Determine the data type based on a related data type

My goal is to develop a versatile function with a generic string union parameter that defines an object parameter where the property name depends on the generic parameter itself.

Consider this scenario: The variable params can be either {customerId: 'some string'} or {supplierId: 'some string'}. I aim to infer T from params, so that when a customerId is provided, the function is invoked as foo<'customer'>:

type Type = 'customer' | 'supplier'
type EncodedType<T extends Type> = `${T}Id`;

// This needs to be a mapped type to prevent becoming Record<'customerId' | 'supplierId', string>, 
// i.e., with both properties always present
type Params<T extends Type> = {
  [P in T]: Record<EncodedType<P>, string>;
}[T]; 

function foo<T extends Type>(params: Params<T>) {
}

However, when calling this as foo({customerId: '42'}), T is inferred as Type, rather than as customer. If I explicitly specify

foo<'customer'>({customerId: '42})
, then the params is validated to meet Params<'customer'>. But without specifying T, it remains as Type, not inferred from the passed object.

I have found success by utilizing two generic parameters:

type DecodedType<T extends EncodedType<Type>> = T extends `${infer P}Id` ? P : never;

function foo2<K extends EncodedType<Type>, T extends DecodedType<K>>(params: Record<K, string>) {
}

foo2({customerId: '42'}) // called as foo2<'customerId', 'customer'>

... TypeScript can infer K from the record and subsequently infer T from

K</code. However, inferring a transformed type from the keys of a record like in the initial example seems challenging.</p>
<p>I prefer to maintain clean functionality with just one generic type. Is it feasible to modify the function to take <T extends Type> and infer <code>T
from the params object?

Edit: As my previous example was oversimplified, here's another quirky demonstration:

type Type = 'foo' | 'bar';
type Params<T extends Type> = {
  foo: {a: number};
  bar: {b: number};
}[T];

declare function func<T extends Type>(params: Params<T>): Uppercase<T>;

const result = func({a: 42});

In this case, T doesn't get inferred into 'foo' or

'bar'</code from the key names in <code>params</code. Instead, <code>T
stays as
'foo' | 'bar'. Consequently, the resulting output of <code>func
will be 'FOO' | 'BAR', instead of merely 'FOO'.

Although there's technically nothing wrong with the code for foo2 above, it irks me that inferring a type

T</code with the value <code>'customer'
from an object parameter by examining the presence of a key named
customerId</code isn't straightforward; only deriving the key name <code>customerId
from the generic parameter customer appears practical.

Answer №1

The foo2() method is successful because it instructs the compiler to deduce the generic type K from a value of type Record<K, string>, which is quite straightforward (similar to using the keyof operator).

It's worth noting that in foo2(), there is no need for T to be a separate generic type parameter. Since there is no inference point for T, it defaults to the constraint of Decoded<K>, allowing you to directly utilize Decoded<K> instead of T:

function foo2<K extends EncodedType<Type>>(params: Record<K, string>) {
   // utilize DecodedType<K> instead of T within this context
}

If you're wondering whether it's possible to reverse this scenario and have T as the primary generic type parameter like in foo(), the answer seems to be "no". This is because deducing the generic type T from a value of type Params<T> or

{ [P in T]: Record<`${P}Id`, string> }[T]
is complex and not easily achievable by the compiler.

Even simplifying the process by removing unions doesn't solve the issue:

type InferKey<T> = T extends Record<`${infer U}Id`, string> ? U : never;
type X = InferKey<{ FooId: "abc" }>
//   ^? type X = string

It appears that expecting the compiler to infer template literal types indirectly is too much, resulting in failed inference and unhelpful constraints. While writing code like foo2() might seem less convenient than foo(), it significantly improves generic type argument inference.

Click here for a Playground link with the 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

Tips for executing DOM manipulation within Angular components

What is the best way to access DOM elements in Angular (Version 2.x and above)? Since basic functions such as addClass and removeClass are not available in typescript, how can we perform DOM manipulations in Angular components? Any suggestions would be g ...

Is it Control or ControlGroup in Angular 2 - How to tell the difference?

let aa = this._formBuilder.control(""); let bb = this._formBuilder.group({ aa: aa }; I am trying to achieve the following: if (typeof(aa) == "Control") { // perform a specific action } else if (typeof(aa) == "ControlGroup") { // perform anoth ...

What specific category does the enum object fall under?

How can I create a wrapper class for a collection of elements in an enumeration? export class Flags<ENUMERATION> { items = new Set<ENUMERATION>(); enu; // what type ? constructor(enu) { // what type ? ...

Searching for nicknames in a worldwide Jest arrangement?

Before running all test cases, I need to execute certain tasks only once. To achieve this, I have created a global function and specified the globalSetup field in my Jest configuration: globalSetup: path.resolve(srcPath, 'TestUtils', 'global ...

The TypeScript command tsc -p ./ is causing errors in the typings modules

Whenever I try to execute the typescript command tsc -p ./, I encounter an error. This issue seems to be occurring with es6-shim and some other node packages. https://i.sstatic.net/9YKHT.png Below is my package.json: "scripts": { "vscode:prepublish ...

Steps to create a TypeScript function that mimics a JavaScript function

As I look at this javascript code: // find the user User.findOne({ name: req.body.name }, function(err, user) { if (err) throw err; if (!user) { res.json({ success: false, message: 'Authentication failed. User not found.' ...

The function is failing to trigger when clicked within an Angular 5 app

Hey everyone, I'm facing a minor issue with my Angular 5 project. I am trying to use a common session check method from a shared TypeScript file. This method is used both when the page loads and when the logout button is clicked to redirect the user t ...

What exactly occurs when a "variable is declared but its value is never read" situation arises?

I encountered the same warning multiple times while implementing this particular pattern. function test() { let value: number = 0 // The warning occurs at this line: value is declared but its value is never read value = 2 return false } My curi ...

I attempted to unsubscribe from an observable in Angular, but I encountered an error stating that the unsubscribe function does not exist

Here is the code snippet from a components.ts file in an Angular project. I encountered the following error during compilation: ERROR merge/merge.component.ts:75:12 - error TS2551: Property 'unsubscribe' does not exist on type 'Observable& ...

Encountered an error with Aurelia webpack 4 when trying to load a necessary CSS file during runtime

I encountered a unique issue with webpack and aurelia that I can't seem to figure out. After creating a new webpack configuration based on online resources and official documentation, the compilation goes smoothly without any errors. However, during r ...

Elevate the scope analysis for a function within the Jasmine framework

I have written a few functions within the app component. I am experiencing an issue with increasing coverage in the summary for these component methods. The test cases are functioning correctly, but some lines are not being accounted for in the coverage s ...

Retrieving source in Angular from an async function output within a specified time limit

I have a quick query :). I'm attempting to retrieve the image src from an async function, but so far, I haven't had much success. This is what I have: <img [src]="getProductImage(articleNumber)"/> and in my TypeScript file: publi ...

DotLottie file loading issues

While using dotlottie/react-player, webpack 4, and react 16, I encountered module parse failed errors during compilation. "@dotlottie/react-player": "^1.6.5" "webpack": "^4.44.2", "react": "16.14.0&qu ...

When working with Typescript, the error "Unable to locate type 'require'" may be encountered

I am attempting to incorporate the type definition file available at https://www.npmjs.com/package/diff-match-patch into my Angularjs project. Just a heads up: I am working with Visual Studio as my IDE var DiffMatchPatch = require('diff-match-patch& ...

Guide on utilizing the useContext hook in React/Next.js while incorporating TypeScript

As I embark on my journey in the world of TypeScript, I find myself working on a new project in Next.js using TypeScript. My goal is to incorporate authentication functionality into this project by utilizing createContext. Coming from a background in JavaS ...

Using Typescript/JSX to assign a class instance by reference

Looking to access an object's property by reference? See the code snippet below; class Point{ x:number; y:number; constructor(x,y) { this.x=x; this.y=y; } } const a = { first: new Point(8,9), second: new Point(10,12) }; let someBoo ...

Issue encountered with the signature provided for a Safe API POST request

Watch this demonstration video of the issue at hand: I have created a signer using my Metamask Private Key and generated a signature from it as shown below: const signer = new ethers.Wallet(PRIVATE_KEY as string, provider) const safeInstance = new ethers. ...

What is the best way to incorporate additional data into a TypeScript object that is structured as JSON?

I'm exploring ways to add more elements to an object, but I'm uncertain about the process. My attempts to push data into the object have been unsuccessful. people = [{ name: 'robert', year: 1993 }]; //I aim to achieve this peopl ...

Angular UI failing to refresh despite Observable subscription activation

I'm facing an issue with my Angular page where the UI is not updating when the observable parameter from a service changes. I've experimented with storing the observable result in a flat value and toggling a boolean to update the UI, but none of ...

Exploring the capabilities of Vue combined with Typescript and Audio Worklets

I've encountered a challenge with configuring Vue to compile audio worklets. Specifically, I am facing a similar issue to this problem that has already been resolved, but using Typescript instead of JavaScript. My approach was to include the ts-loader ...