Determine the type of input and output based on another argument

When working with a function that takes an object of either TypeA or TypeB, the first parameter is used to specify the type of the object and the returned type depends on this first parameter.

The issue arises in TypeScript where the type of the object is not inferred within a case (or if) statement, resulting in errors in the following code.

If you're like me and prefer to avoid explicit casting (such as adding as TypeA), are there any alternative solutions available?

type TypeA = {
  input: { foo: string }
  output: { foo: number }
}

type TypeB = {
  input: { bar: string }
  output: { bar: number }
}

type Types = {
  TypeA: TypeA
  TypeB: TypeB
}

type TypesName = keyof Types

/**
 * Handles TypeA or TypeB objects, returning the appropriate type based on the input.
 */
function transform<N extends TypesName>(typeName: N, typeValue: Types[N]['input']): Types[N]['output'] {
  switch (typeName) {
    case 'TypeA':
      return transformTypeA(typeValue) // Error message about assigning types may appear here
    case 'TypeB':
      return transformTypeB(typeValue) // Error message about assigning types may appear here
  }
  throw new Error('Unknown type')
}

function transformTypeA(typeA: TypeA['input']): TypeA['output'] {
  return { foo: parseInt(typeA.foo) }
}

function transformTypeB(typeB: TypeB['input']): TypeB['output'] {
  return { bar: parseInt(typeB.bar) }
}


const transformedValue = transform('TypeA', { foo: 'lol' })
console.log(transformedValue) // The transformedValue is now a type of { foo: number }

Answer №1

Although there is a potential solution, it may not be beneficial at this moment.

type Union<N extends keyof Types = keyof Types> = N extends N ? [typeName: N, typeValue: Types[N]['input']]: never;

function transform<N extends TypesName>(...p: Union<N>): Types[N]['output'] {
  switch (p[0]) {
    case 'TypeA':
      return transformTypeA(p[1])
    case 'TypeB':
      return transformTypeB(p[1])
  }
}

Playground Link

To ensure TypeScript recognizes the relationship between the two values, we need to utilize index access instead of defining separate parameters or destructuring them. This limitation might be addressed in the future, as mentioned here

The current pattern of deconstructing a discriminant property and payload property into distinct variables without establishing a connection is not supported due to limitations in the control flow analyzer. For instance:

type Data = { kind: 'str', payload: string } | { kind: 'num', payload: number };

function foo({ kind, payload }: Data) {
    if (kind === 'str') {
        payload.length;  // Error, payload not narrowed to string
    }
}

Possibly support for this pattern may be introduced later, but not within this PR.

Answer №2

In TypeScript version 4.6, developers can now utilize the feature requested in this wishlist item about supporting correlated union types #30581

type TypeA = {
  input: { foo: string }
  output: { foo: number }
}

type TypeB = {
  input: { bar: string }
  output: { bar: number }
}

type Types = {
  TypeA: TypeA
  TypeB: TypeB
}

type TypeMap = { 
  TypeA: TypeA, 
  TypeB: TypeB 
};

type TypeMapAsGeneric<K extends keyof TypeMap = keyof TypeMap> = { [P in K]: TypeMap[P] }[K];

function transformTypeA(typeA: TypeA['input']): TypeA['output'] {
  return { foo: parseInt(typeA.foo) }
}

function transformTypeB(typeB: TypeB['input']): TypeB['output'] {
  return { bar: parseInt(typeB.bar) }
}

const transformers: { [K in keyof TypeMap]: (data: TypeMapAsGeneric<K>['input']) => TypeMapAsGeneric<K>['output'] } = {
  TypeA: transformTypeA,
  TypeB: transformTypeB
};


const transform = <K extends keyof TypeMap>(typeName: K, inputValue: TypeMapAsGeneric<K>['input']): TypeMapAsGeneric<K>['output'] => {
  const transformer = transformers[typeName];
  return transformer(inputValue);
}


const transformedValue = transform('TypeA', { foo: '123' })
console.log(transformedValue)

Playground 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

Utilizing const as the iteration variable in a for loop

I've grasped the concept of using var and let in a for loop in typescript/javascript, but can someone shed light on how and why a const variable as a loop variable behaves? for (const i = 0; i < 5; i++) { setTimeout(function() { console.log( ...

Angular error TS2531: Object may be null

Within my Component.html file, I have an input field set up like this: <input type="text" (change) = "setNewUserName($event.target.value)"/> The code within the component.ts file looks like this: import { Component } from "@ ...

Unable to modify the text value of the input field

I am currently learning how to code in React, so please forgive me if my question seems basic. I am attempting to change the text of an Input element based on the value of the filtered variable, like this: const contactContext = useContext(ContactContext); ...

A step-by-step guide on reversing options in the Ant Design Cascader component

By default, the Cascader component's options are nested from left to right. I am looking to have them go from right to left instead. However, I could not find anything in the component's API that allows for this customization. Is it even possibl ...

After integrating session store into my application, nestjs-sequelize is not synchronizing with any models

I'm currently working on developing a Discord bot along with a website dashboard to complement it. Everything is running smoothly, except for the backend Nestjs API that I am in the process of creating. I decided to use Sequelize as the database for m ...

What are the best scenarios for creating a constructor in Angular 2 using Typescript?

Check out these sample constructors I found in the Angular 2 documentation: export class AppComponent implements OnInit { title = 'Tour of heroes'; heroes: Hero[]; selectedHero: Hero; constructor(private heroService: HeroService ...

ionChange - only detect changes made in the view and update the model in Ionic 2

In my Ionic 2 application, I have a feature that allows users to schedule notifications as reminders. The requirements for this feature are as follows: Upon entering the reminder page, it should check if there is a saved reminder. If a reminder is saved ...

The TypeScript error message is saying that it cannot find the property 'push' of an undefined value, resulting in a error within the

Issue: Unable to access property 'push' of undefined in [null]. class B implements OnInit { messageArr: string[]; ngOnInit() { for(let i=0; i<5; i++) { this.messageArr.push("bbb"); } } } ...

The error states that the type '() => string | JSX.Element' cannot be assigned to the type 'FC<{}>'

Can someone help me with this error I'm encountering? I am fairly new to typescript, so I assume it has something to do with that. Below is the code snippet in question: Any guidance would be greatly appreciated. const Pizzas: React.FC = () => { ...

An issue arises following an upgrade in Angular from version 9 to version 10, where the property 'propertyName' is being utilized before it has been initialized

I've spent time looking on Google, Github, and Stackoverflow for a solution to this error, but I'm still struggling to fix it. Can anyone offer a suggestion or help? Recently, I upgraded my Angular project from version 9 to version 10, and after ...

Changing an Angular template.html into a PDF document within an Angular 2 application can be achieved by utilizing

Exploring Angular 2 and looking for a way to export my HTML component in Angular 2 to PDF using jspdf. I want to convert dynamically generated tabular HTML into a PDF using jspdf. Below is a snippet of sample code along with a Plunker link: import {Comp ...

Is tsconfig.json necessary for JS libraries without TypeScript to include a .d.ts file when shipping?

I am currently in the process of creating a .d.ts file for an established JavaScript library that does not utilize the TypeScript compiler or include any TypeScript code. Should I include a tsconfig.json file in the library to ensure proper interpretation ...

Dealing with null exceptions in Angular 4: Best practices

Hi there, I am encountering an issue with binding my model data to HTML fields where when I try to edit the data it returns an error saying "cannot read value of null". How can I resolve this? Here is the HTML code snippet: <div class="form-group"> ...

"NODEJS: Exploring the Concept of Key-Value Pairs in Object

I am facing a challenge with accessing nested key/value pairs in an object received through a webhook. The object in req.body looks like this: {"appId":"7HPEPVBTZGDCP","merchants":{"6RDH804A896K1":[{"objectId&qu ...

Is it possible to integrate Firebase Storage into a TypeScript/HTML/CSS project without the use of Angular or React?

For my project, I am aiming to create a login and register page using TypeScript. Currently, my code is functioning well even without a database. However, I would like to implement Firebase for storing user credentials so that the login process becomes mor ...

Unlocking the power of variables in Next.js inline sass styles

Is there a way to utilize SASS variables in inline styles? export default function (): JSX.Element { return ( <MainLayout title={title} robots={false}> <nav> <a href="href">Title</a> ...

Angular is unable to fetch the chosen option from a dropdown selection

Recently, I encountered an issue with a module form that appears as a pop-up. Within this form, users can input data required to create a new object of a specific class. One field allows users to select a ventilation zone for the new room object being crea ...

Even when it appears to be chaotic, the TypeScript array of numbers always manages to find its way back to being sorted

I recently developed a function that aims to verify if an array of numbers is sorted: const checkIfSorted = (numbers: number[]) => { return numbers === numbers.sort((a, b) => a - b); }; checkIfSorted([4, 2, 8, 7, 3, 10, 1, 5, 9, 6]); // This cur ...

In my attempt to assess the correlation between value 1 and a value in the preceding object, I am utilizing the *ngFor directive

Attempting to compare 2 entries in an *ngFor loop. The code should compare the value at the current object to a value at the previous object. <ng-container *ngFor="let item of s_1.comments[0]; index as b"> <article class="message i ...

Scroll automatically to the last div whenever a button is clicked using React and Typescript

I'm currently working on an application being developed in React + Typescript. I am trying to implement auto-scroll functionality to the last div within a parent div where child divs are dynamically added based on data from an array of objects. The da ...