What is the best way to select types conditionally based on the presence of a property in another type?

To begin with, I have a specific desired outcome:

type Wrapper<ID extends string> = { id: ID };
type WrapperWithPayload<ID extends string, Payload> = { id: ID, payload: Payload };

enum IDs {
  FOO = "ID Foo",
  BAR = "ID Bar",
  BAZ = "ID Baz"
}

interface AssociatedTypes {
  [IDs.FOO]: number;
  [IDs.BAR]: number[];
}

type Result = MagicMapper<IDs, AssociatedTypes>
/*
 * Result should have this type:
 * {
 *   [IDs.FOO]: WrapperWithPayload<IDs.FOO, number>;
 *   [IDs.BAR]: WrapperWithPayload<IDs.BAR, number[]>;
 *   [IDs.BAZ]: Wrapper<IDs.BAZ>;
 }
 */

In simple terms, I wish to provide strings and a mapping where some strings are associated with another type. Then, I aim to generate a new type using the supplied strings as keys. If there is a mapping for a specific string/key, I want to use one type, if not, then another type.

Currently, my approach is like this:

type MagicMapper<T extends string, Mapping extends { [key in T]?: any }> = {
    [key in T]: Mapping[key] extends never ? Wrapper<key> : WrapperWithPayload<key, Mapping[key]>;
};

And it almost achieves the intended result:

type Result = {
  "ID Foo": WrapperWithPayload<IDs.FOO, number>;
  "ID Bar": WrapperWithPayload<IDs.BAR, number[]>;
  "ID Baz": Wrapper<IDs.BAZ> | WrapperWithPayload<IDs.BAZ, any>;
}

The error lies in the union on the key for Baz. I suspect the issue stems from the extends never condition, but changing it to, for example, undefined only exacerbates the situation:

type MagicMapper<T extends string, Mapping extends { [key in T]?: any }> = {
    [key in T]: Mapping[key] extends undefined ? Wrapper<key> : WrapperWithPayload<key, Mapping[key]>;
};

type Result = {
  "ID Foo": Wrapper<IDs.FOO>;
  "ID Bar": Wrapper<IDs.BAR>;
  "ID Baz": Wrapper<IDs.BAZ>;
}

Is there a way to align things to meet my requirements?

Answer №1

To verify the presence of a key in Mapping, modify the test to

Mapping extends { [P in key]: infer U }
. This adjustment will ensure that everything functions as intended:

type Wrapper<ID extends string> = { id: ID };
type WrapperWithPayload<ID extends string, Payload> = { id: ID, payload: Payload };

enum IDs {
    FOO = "ID Foo",
    BAR = "ID Bar",
    BAZ = "ID Baz"
}

interface AssociatedTypes {
    [IDs.FOO]: number;
    [IDs.BAR]: number[];
}

type MagicMapper<T extends string, Mapping extends { [key in T]?: any }> = {
    [key in T]: Mapping extends { [P in key]: infer U } ? WrapperWithPayload<key, U> : Wrapper<key>;
};
type Result = MagicMapper<IDs, AssociatedTypes> 
// equivalent to:
type Result = {
    "ID Foo": WrapperWithPayload<IDs.FOO, number>;
    "ID Bar": WrapperWithPayload<IDs.BAR, number[]>;
    "ID Baz": Wrapper<IDs.BAZ>;
}

Interactive demo link

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

Assign the private members of the class to the arguments of the constructor

class Bar { #one #two #three #four #five #six #seven #eight #nine #ten #eleven #twelve #thirteen #fourteen #fifteen #sixteen constructor( one, two, three, four, five, six, seven, eight, ...

The service fails to recognize the ActivatedRoute

Using ActivatedRoute in Services The Challenge Attempting to utilize ActivatedRoute within a service, I encountered an issue where it was not tracking the current route accurately. It seemed unable to detect any route at all. After spending considerable ...

Struggling to retrieve posted data using Angular with asp.net

I have encountered an issue while sending a post request from Angular to my ASP.NET server. I am trying to access the values of my custom model class (SchoolModel) and I can see that all the values are correct inside Angular. However, when I attempt to ret ...

What is the proper way to declare a Type for a JSX attribute in Google AMP that utilizes square brackets?

When utilizing AMP's binding feature, you must apply specific attributes that encapsulate an element's property with square brackets and connect it to an expression. An example from AMP is shown below: <p [text]="'Hello ' + foo"> ...

Typescript i18next does not meet the requirement of 'string | TemplateStringsArray NextJS'

While attempting to type an array of objects for translation with i18next, I encountered the following error message in the variable navItems when declaring i18next to iterate through the array Type 'NavItemProps[]' does not satisfy the constrain ...

The @output decorator in Angular5 enables communication between child and

Hello fellow learners, I am currently diving into the world of Angular and recently stumbled upon the @output decorators in angular. Despite my best efforts to research the topic, I find myself struggling to fully grasp this concept. Here's a snippet ...

Prevent Promise / Property not found error in Angular2 by Instantiating Class

When working with my "export class", I encountered an issue that led to a Promise error if I didn't include this line of code: purchase = new Purchase(); right before the constructor. The error indicated that the property "name" was not found. Alth ...

Discovering the position of an element within an array and leveraging that position to retrieve a corresponding value from a separate array

const STATE = ["TEXAS","CALIFORNIA","FLORIDA","NEW YORK"] const STATE_CODE = ["TX","CA","FL","NY"] With two arrays provided, the first array is displayed in a dropdown menu. The challenge is to retrieve the corresponding state code from the second array ...

Replacing URLs in Typescript using Ionic 3 and Angular

Currently facing some difficulties getting this simple task to work... Here is the URL format I am dealing with: https://website.com/image{width}x{height}.jpg My objective is to replace the {width} and {height} placeholders. I attempted using this func ...

Execute an Asynchronous Operation in NgRx After Triggering an Action

Please note that this is a question seeking clarification Instructions Needed I am currently working on dispatching an action to NgRx in order to add a task to a list of tasks. Additionally, I need to perform a put request to an API to save the changes ma ...

What steps can I take to guarantee that the observer receives the latest value immediately upon subscribing?

In my Angular 2 and Typescript project, I am utilizing rxjs. The goal is to share a common web-resource (referred to as a "project" in the app) among multiple components. To achieve this, I implemented a service that provides an observable to be shared by ...

Looking for a specific phrase in the data entered by the user

I am dealing with data in ckeditor that looks like this: <p>test 1</p> <p>test 2</p> <p><img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICw ...

What is the syntax for using typeof with anonymous types in TypeScript?

After reading an article, I'm still trying to grasp the concept of using typeof in TypeScript for real-world applications. I understand it's related to anonymous types, but could someone provide a practical example of how it can be used? Appreci ...

Guide to implementing a specified directive value across various tags in Angular Material

As I delve into learning Angular and Material, I have come across a challenge in my project. I noticed that I need to create forms with a consistent appearance. Take for example the registration form's template snippet below: <mat-card> <h2 ...

What is the best way to dynamically translate TypeScript components using transloco when the language is switched?

I am seeking clarification on how to utilize the TranslocoService in TypeScript. Imagine I have two lang JSON files for Spanish and Portuguese named es.json and pt.json. Now, suppose I have a component that displays different labels as shown in the followi ...

Errors can occur when using TypeScript recursive types

Below is a simplified version of the code causing the problem: type Head<T> = T extends [infer U,...unknown[]] ? U : never; type Tail<T> = T extends [unknown,...infer U] ? U : []; type Converter = null; type Convert<T, U extends Converter& ...

Blending ASP.NET Core 2.0 Razor with Angular 4 for a Dynamic Web Experience

I am currently running an application on ASP.NET Core 2.0 with the Razor Engine (.cshtml) and I am interested in integrating Angular 4 to improve data binding from AJAX calls, moving away from traditional jQuery methods. What are the necessary steps I need ...

Unable to connect input with abstract classes at a hierarchy depth of 2 levels or more

When working on my Angular application: If a Component utilizes an Input that is defined in its immediate parent (abstract) class, everything runs smoothly. However, if a Component uses an Input that is declared in a parent class located two levels a ...

Angular HttpClient mapping causes the removal of getters from the target object

Utilizing the HttpClient to fetch Json data from an API, I am utilizing the autoMapping feature of the HttpClient to map the json response to a specified object in this manner: this.httpClient.post<Person>(url, body, { headers: headers, params: http ...

The attribute 'positive_rule' is not found within the structure '{ addMore(): void; remove(index: any): void;'

data() { return { positive_rule: [ { positive_rule: "", }, ], }; }, methods: { addMore() { this.positive_rule.push({ positive_rule: "", }); }, ...