Using TypeScript Generics to Automatically Infer Types Based on Field Values

enum Letter {
  A = "A",
  B = "B",
  C = "C"
}

export type EntityT<T extends Letter = Letter> = T extends Letter.A
  ? { a: number }
  : T extends Letter.B
  ? { b: string }
  : T extends Letter.C
  ? { c: string }
  : never;

type DeltaT<T extends EntityT> = _DeltaT<T, keyof T>;

export type _DeltaT<E extends EntityT, K extends keyof E> = {
  key: K;
  oldValue: E[K];
  newValue: E[K];
};

export type ProblemT<T extends Letter = Letter> = EntityT<T> extends infer E
  ? {
      id: string;
      type: T;
      delta: DeltaT<E>[];
      oldEntity: E;
      newEntity: E;
    }
  : never;

const testCase: ProblemT<Letter.A> = {
  id: "id",
  type: Letter.A,
  delta: [{ key: "a", oldValue: 1, newValue: 2 }],
  oldEntity: { a: 1 },
  newEntity: { a: 2 }
};

function myFunc<T extends Letter>(e: ProblemT<T>) {
  switch (e.type) {
    case Letter.A: {
      const uselessConversion = e as ProblemT<Letter.A>;
      return uselessConversion.newEntity.a;
    }
    case Letter.B: {
      const uselessConversion = e as ProblemT<Letter.B>;
      return uselessConversion.newEntity.b;
    }
    case Letter.C: {
      const uselessConversion = e as ProblemT<Letter.C>;
      return uselessConversion.newEntity.c;
    }
  }
}

I would like to define the type ProblemT based on its type property, returning different values in oldEntity and newEntity. I aim to use the switch within myFunc without manual conversion of objects, letting TypeScript infer the type based on the type. However, this does not work as expected. How can I make the switch automatically infer the type without the need for manual conversions? Is there a more effective way to declare ProblemT with different types so that inference works with the switch?

Here is a link to the TypeScript playground

Answer №1

Consider that variations like Letter.A | Letter.B also inherit from Letter, which means the compiler may not fully understand the constraints in place.

To ensure proper functionality, you can implement something along these lines:

enum Letter {
  A = "A",
  B = "B",
  C = "C"
}

export type EntityT<T extends Letter> =
  T extends Letter.A ? { a: number }
  : T extends Letter.B ? { b: string }
  : T extends Letter.C ? { c: string }
  : never;

export type DeltaT<T extends Letter, K = keyof EntityT<T>> = K extends keyof EntityT<T>
  ? {
    key: K;
    oldValue: EntityT<T>[K];
    newValue: EntityT<T>[K];
  }
  : never;

export type ProblemT<T extends Letter> = {
  id: string;
  type: T;
  delta: DeltaT<T>[];
  oldEntity: EntityT<T>;
  newEntity: EntityT<T>;
};

function myFunc(e: ProblemT<Letter.A> | ProblemT<Letter.B> | ProblemT<Letter.C>) {
  switch (e.type) {
    case Letter.A: return e.newEntity.a;
    case Letter.B: return e.newEntity.b;
    case Letter.C: return e.newEntity.c;
  }
}

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

Encountering "Duplicate identifier" issues when using Angular2 and TSD

I am currently in the process of migrating a project from Angular 1 to Angular 2. The project involves client and server code, with some shared components. My goal is to implement Angular 2 on the client side by following the guidelines outlined in the ng2 ...

Using TypeScript with Angular UI Router for managing nested named views in the application

Hey there! I am new to typescript and have a bit of experience with Angular. Lately, I've been struggling to make a common angular-ui-router setup work with typescript. I have a nested named views structure that just doesn't seem to load correctl ...

The Primeng Angular2 checkbox malfunctioning issue

My form setup in Angular2 CLI looks like this: Inside the component export class UsersAddComponent implements OnInit { ngOnInit() { this.userForm = this._formBuilder.group({ role: ['', [Validators.required]], others: this._for ...

Enhance your coding experience with TypeScript's autocomplete in Visual Studio Code

After migrating a project from JavaScript to TypeScript, I am not seeing autocomplete suggestions or type hints when hovering over variables in Visual Studio Code editor (Version 1.7.2). Even the basic example provided below does not display any auto-com ...

Strange Behavior of ngIf and @Input in Angular 2

Despite console indicating false, NgIf appears to always evaluate as true. The issue stems from the component's HTML below: <product-component-tree itemSku="{{item.itemSku}}" selectable="false" classes="col-md-12 col-xs-12"></product-compo ...

When a module is generated, it appends an additional slash to the path in the app.module.ts file

I've noticed a strange behavior with the generator in Angular CLI tools that adds an extra slash character for modules. For example, when running ng generate component visual a line like this is added to the app.module.ts file import { VisualCo ...

What is the correct method for importing a Node module into Angular using TypeScript or AngularCLI?

As I integrate some "legacy" (non-typescript) JavaScript libraries into my Angular single page application. Usually, I simply include a CDN link in the index.html file like this: <script src="//cdnjs.cloudflare.com/ajax/libs/pako/1.0.6/pako.min.js"> ...

Exploring the integration of function interfaces and callbacks to effectively utilize return values

Currently, I am delving into the realm of TypeScript and attempting to grasp the concept of interfaces for functions. My focus is on creating an interface that can optionally accept a Context asserted object, allowing it to function both inside and outside ...

What is the best method for enhancing Express types?

One of my files is named types/express.d.ts declare namespace Express { export interface Response { respondWith(data: any): Response; } } I also have this code snippet in app.js Express.response.respondWith = function(data) { return this.json( ...

Uncovering the Magic of TypeScript Type Inference with Generics and Function Objects

As I attempted to create a versatile function that can accept an interface containing both functions and data, there seems to be an issue with inference. Assistance in resolving this problem would be greatly appreciated. To view the code that is failing t ...

Using Angular to subscribe to a service that employs http requests

I am completely new to angular and typescript, and I'm facing a challenge. Within my service, I have a function: login(email: String, password: String) { let formData = { usuario : email, senha : password, retsession : true } ...

Error TS2403: All variable declarations following the initial declaration must be of the same type in a React project

While developing my application using Reactjs, I encountered an error upon running it. The error message states: Subsequent variable declarations must have the same type. Variable 'WebGL2RenderingContext' must be of type '{ new (): WebGL2 ...

Selecting the optimal data structure: weighing the benefits of using boolean check versus array .include (balancing performance and redundancy

My objects can have one or more properties assigned, with a total of 5 different properties in my case. To illustrate this, let's use a simple movie example where each movie can be assigned from 5 different genres. I have come up with two methods to ...

What is the best way to implement a custom NgbDateParserFormatter from angular-bootstrap in Angular 8?

Currently, I am working on customizing the appearance of dates in a form using Angular-Bootstrap's datepicker with NgbDateParserFormatter. The details can be found at here. My goal is to display the date in the format of year-month-day in the form fi ...

Using the useReducer hook, what is the best way to perform type checking on the action

I have been searching through various resources on stackoverflow without finding a solution to my current issue. My knowledge of TypeScript is limited, which is causing errors. When I leave action: any in my reducer, the error disappears. However, if I sp ...

Removing keys from a generic object using Typescript

Currently, I'm working on creating a higher-order function called without that will generate a new object without the specified keys. I am struggling with getting the keys in the reducer to be of type keyof T as Object.keys returns an array of string ...

The minimum and maximum validation functions are triggered when I am not utilizing array controls, but they do not seem to work when I use array controls

Take a look at the stack blitz example where min and max validation is triggered: https://stackblitz.com/edit/angular-mat-form-field-icrmfw However, in the following stack blitz with an array of the same controls, the validation does not seem to be worki ...

typescript support for the appwrite web sdk

Currently, I am experimenting with the demo-todo-with-react project using TypeScript. I encountered an issue while creating api.tsx based on api.js. The error message states that "Type '{database: Database; account: Account;}' is not assignable t ...

Stop the angular transpiler from removing `debugger` statements

Currently, I'm developing an Angular/Typescript package and testing it locally by using the ng serve command. However, I've noticed that the transpiler seems to be applying production settings, resulting in the removal of debugger statements and ...

Error: Unable to locate the file "./pages/layout/Layout" in the path "src/index.tsx"

After cloning the source code from GitHub and deploying it to Azure cloud, I encountered an error when running the command "azd up." Here are the relevant portions of my code: import ReactDOM from 'react-dom/client'; import { createHashRouter, Ro ...