Determine the precise category based on an object linked to a particular key of another object

In my code, I have a registry object structured as {[key]: <T>()=>T}. The values of T can vary, allowing me to easily add more entries in the future. I also have a function that takes a registry key as a string input and is supposed to return a value of the corresponding generic type. However, I'm encountering an issue where it returns a union of all generic types from the functions in the registry.
To simplify this explanation, I hope a basic working example effectively conveys my intentions.

type valFn<T> = () => T

declare const stringFn: valFn<string>;
declare const numberFn: valFn<number>;

const registry = {
  alpha: stringFn,
  beta: numberFn,
  // ... additional records can be easily added without having to modify the +registryEater+ function every time, with all records following the pattern +key: ValFn<Something>+
};

// CHANGES CAN BE MADE ONLY DOWN FROM HERE
type ReturnType<T> = T extends valFn<infer R> ? R : never
const registryEater = <T extends keyof typeof registry>(registryKey: T): ReturnType<typeof registry[T]> => {
  const desiredFn = registry[registryKey];
  const desiredValue = desiredFn();
  // TS2322 error points out that +desiredValue+ is string | number, but I need it to match the specific type based on the actual value of +registryKey+
  return desiredValue;
};
// CHANGES CAN BE MADE ONLY UP FROM HERE


const thisIsString = registryEater('alpha');
const thisIsNumber = registryEater('beta');

To resolve this issue, one approach is changing return desiredValue; to

return desiredValue as ReturnType<typeof registry[T]>;
. But is there a cleaner solution available?

Answer №1

When it comes to conditional types that rely on generic type parameters, such as

ReturnType<typeof registry[T]>
, the compiler faces limitations in carrying out thorough type analysis. To ensure your function is comprehensible to the compiler, consider restructuring it using distributive object types detailed in microsoft/TypeScript#47109. The concept involves initially creating a basic mapping type like

interface RegistryReturn {
    alpha: string;
    beta: number;
}

and then defining your other actions as mapped types based on it:

type Registry = { [K in keyof RegistryReturn]: () => RegistryReturn[K] }
const registry: Registry = {
    alpha: stringFn,
    beta: numberFn
}

Your generic function can now simply accesses this mapped type and everything functions smoothly:

const registryEater = <K extends keyof RegistryReturn>(
    registryKey: K
): RegistryReturn[K] => {
    const desiredFn = registry[registryKey];
    const desiredValue = desiredFn();
    return desiredValue; // no issues
};

The variable desiredFn is recognized as type Registry[K], while desiredValue() is acknowledged as type

RegistryReturn[K]</code, thanks to the defined mapped type in microsoft/TypeScript#47109.</p>
<hr />
<p>Although this setup compiles as expected, it unfortunately redefines <code>registry
in terms of Registry, which may not be permissible. Your stipulation might require that registry remains unchanged. In such cases, you can establish the RegistryReturn mapping type based on it:

type RegistryReturn = { [K in keyof typeof registry]: 
  ReturnType<typeof registry[K]> 
}

Simply assign registry to a new variable of type

Registry</code ensuring the mapped type lookup continues without alteration:</p>
<pre><code>const registryEater = <K extends keyof RegistryReturn>(
    registryKey: K
): RegistryReturn[K] => {
    const _registry: Registry = registry; // new variable
    const desiredFn = _registry[registryKey];
    const desiredValue = desiredFn();
    return desiredValue; // still no issues
};

This compilation remains unaffected. All that's left is guaranteeing callers get the intended results:

const thisIsString: string = registryEater('alpha'); // all clear
const thisIsNumber: number = registryEater('beta'); // everything works

All set!

Link to Playground for code testing

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

What is the significance of parentheses when used in a type definition?

The index.d.ts file in React contains an interface definition that includes the following code snippet. Can you explain the significance of the third line shown below? (props: P & { children?: ReactNode }, context?: any): ReactElement<any> | nu ...

Preventing data loss in an Ionic array - encountering issues with using this.array.push

When attempting to use the storage get method to fill the array storedArr = [], I encounter the error message .push is not a function: storedArr = this.storage.get('stored') ? this.storage.get('stored').then((e) => {e}) : []; The c ...

The Angular2 Observable fails to be activated by the async pipe

Take a look at this simple code snippet using angular2/rxjs/typescript public rooms: Observable<Room[]>; constructor ( ... ) { this.rooms = this.inspectShipSubject .do(() => console.log('foo')) .switchMap(shi ...

Unable to successfully remove item using Asyncstorage

const deleteProduct = prod => { Alert.alert( 'Delete Product', `Are you sure you want to remove ${prod.id}?`, [ { text: 'Cancel', style: 'cancel', }, { ...

Handling exception type in child_process exec method - NodeJS with Typescript integration

Check out this sample code: const execPromise = util.promisify(exec); try { const { stdout } = await execPromise(cliCommand); } catch (error) { if (error instanceof S3ServiceException) { // error message is not handled correctly console ...

What makes TS unsafe when using unary arithmetic operations, while remaining safe in binary operations?

When it comes to arithmetic, there is a certain truth that holds: if 'a' is any positive real number, then: -a = a*(-1) The Typescript compiler appears to have trouble reproducing arithmetic rules in a type-safe manner. For example: (I) Workin ...

What is the proper way to define the type when passing a function as a component prop, with or without parameters?

import { dehydrate, HydrationBoundary } from '@tanstack/react-query'; import getQueryClient from '@/lib/react-query/getQueryClient'; export async function RQBoundary<T>({ children, queryKey, fn, }: { children: React.Reac ...

Issue with displaying response data from Angular API in the template

I am retrieving categories from the backend and trying to display them in a table. However, there seems to be an issue with my code as no data is showing up in the table. Although the getFournisseurs method is successfully fetching 5 items from the backen ...

A guide on implementing Redux Toolkit in an SPFX (typescript) project

I am currently working on developing SPFX components using typescript and webpack. My goal is to integrate redux toolkit into my project, but it is causing compilation errors. Here is what I have done so far: Created the project structure using yo Insta ...

Exploring the world of Typescript TSX with the power of generic

Introducing JSX syntax support in Typescript opens up new possibilities. I have an expression that functions smoothly with traditional *.ts files, but encounters issues with *.tsx files: const f = <T1>(arg1: T1) => <T2>(arg2: T2) => { ...

Unable to retrieve a substring value in Angular using Typescript

html <p> <input type="text" maxlength="40" (input)="recipientReference = deleteSpacing(recipientReference)" [(ngModel)]="recipientReference" style="width: 30vw; padding: 5px;border: 1px solid;border ...

Exploring the methods of connecting with data-checked and data-unchecked attributes in Angular

Utilizing a toggle switch, I am able to determine what text to display in the div by utilizing html attributes such as data-checked and data-unchecked. In addition, I have an Angular pipe that translates all texts on the website based on the selected lang ...

Leveraging Angular 4 with Firebase to extract data from a database snapshot

My dilemma lies in retrieving data from a Firebase DB, as I seem to be facing difficulties. Specifically, the use of the "this" operator within the snapshot function is causing issues (highlighted by this.authState.prenom = snapshot.val().prenom) If I att ...

Can you provide me with the round-the-clock regular expressions for the 'angular2-input-mask' plugin?

Need assistance with inputting 24-hour time format using 'angular2-input-mask'. Currently using the following mask. What is the correct mask for a valid 24-hour time format? this.mask = [/[0-2]/, /^([0-9]|2[0-3])/, ':', /[0-5]/, /[0-9 ...

How long does it take to delete and recreate a cloudfront distribution using AWS CDK?

I am currently undergoing the process of migrating from the AWS CDK CloudfrontWebDistribution construct to the Distribution Construct. According to the documentation, the CDK will delete and recreate the distribution. I am curious about the total duration ...

Can someone please provide guidance on how I can access the current view of a Kendo Scheduler when switching between views, such as day view or week

<kendo-scheduler [kendoSchedulerBinding]="events" [selectedDate]="selectedDate" [group]="group" [resources]="resources" style="height: 600px;" [workDayStart]="workDayStart" [workDayEnd] ...

Using React and TypeScript to Consume Context with Higher Order Components (HOC)

Trying to incorporate the example Consuming Context with a HOC from React's documentation (React 16.3) into TypeScript (2.8) has been quite challenging for me. Here is the snippet of code provided in the React manual: const ThemeContext = React.creat ...

Every time I click on a single button, all the text inputs get updated simultaneously

One issue I encountered is with a component featuring increment and decrement buttons. These buttons are meant to interact with specific products, yet when clicked, all text inputs update simultaneously instead of just the intended one. COMPONENT HTML: &l ...

angular 6's distinctUntilChanged() function is not producing the desired results

I have a function that retrieves an observable like so: constructor(private _http: HttpClient) {} getUsers(location){ return this._http.get(`https://someurl?location=${location}`) .pipe( map((response: any) => response), ...

Should an HTML canvas in Angular be classified as a Component or a Service?

I have a basic drawing application that uses an MVC framework in TypeScript, and I am looking to migrate it to Angular. The current setup includes a Model for data handling, a View for rendering shapes on the canvas, and a Controller to manage interactio ...