Thorough exploration of a collection of varied, categorized items

My goal is to have TypeScript ensure that all cases are covered when mapping over a union like this:

type Union = 
  { type: 'A', a: string } |
  { type: 'B', b: number }

The handler for the Union:

const handle = (u: Union): string =>
  theMap[u.type](u);

It would be ideal if we could find a way to incorporate an exhaustiveness check from TypeScript here:

const theMap: { [a: string]: (u: Union) => string } = {
  A: ({a}: { type: 'A', a: string }) => 'this is a: ' + a,
  B: ({b}: { type: 'B', b: number }) => 'this is b: ' + b
};

Answer №1

REVISION FOR TS2.8+

Ever since the introduction of conditional types, streamlining the process of strongly typing theMap has become much simpler. Utilizing Extract<U, X>, we can now extract only those elements from a union type U that are assignable to X:

type Union = { type: "A"; a: string } | { type: "B"; b: number };

const theMap: {
  [K in Union["type"]]: (u: Extract<Union, { type: K }>) => string
} = {
  A: ({ a }) => "this is a: " + a,
  B: ({ b }) => "this is b: " + b
};

This method is straightforward. Unfortunately, as of TS2.7 or later, calling theMap(u.type)(u) is no longer permitted by the compiler. The relationship between the function theMap(u.type) and value u is considered 'correlated', but this correlation goes unnoticed by the compiler. Instead, it treats theMap(u.type) and u as independent union types, necessitating a type assertion for one to call the other:

const handle = (u: Union): string =>
  (theMap[u.type] as (v: Union) => string)(u); // this assertion is necessary

Alternatively, you could manually navigate through potential union values like so:

const handle = (u: Union): string =>
  u.type === "A" ? theMap[u.type](u) : theMap[u.type](u); // redundant

I usually recommend using assertions for such cases.

An open issue addressing these correlated types exists, although the possibility of future support remains uncertain. Best of luck with your endeavors!


Answer for TS2.7 and earlier:

With the given definition of the Union type, TypeScript struggles to simultaneously enforce both an exhaustive check on theMap (ensuring there's exactly one handler for each constituent type of the union) and a soundness constraint (each handler within theMap corresponds to a specific constituent type of the union).

However, utilizing a more general type allows us to express and maintain these constraints effectively. Let's first explore the generalized type:

type BaseTypes = {
  A: { a: string };
  B: { b: number };
}
...

That was quite a bit to digest, but hopefully, it provides some clarity. Best of luck moving forward!

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

Setting a custom port for your Node.js Typescript project on Heroku

In the usual case, Heroku dynamically assigns a port for us. const PORT : string|number = process.env.PORT || 5000; However, how can I alter this code to handle the PORT... utilizing the => TypeScript shorthand? server.listen(port => { console.l ...

In Angular, the process of duplicating an array by value within a foreach function is not

I have been attempting to duplicate an array within another array and make modifications as needed. this.question?.labels.forEach((element) => { element["options"] = [...this.question?.options]; // I've tried json.stringify() as wel ...

Encountering an error stating "Argument of type 'X' is not assignable to parameter of type 'X' in the NextApiResponse within NextJS."

Here is the code snippet I am working with: type Data = { id: string; name: string; description: string; price: number; }; const FetchMeals = async (req: NextApiRequest, res: NextApiResponse<Data>) => { const response = await fetch( ...

Utilizing Google Closure Library with Angular 6

I am looking to integrate the google closure library into my angular 6 application. To achieve this, I have utilized the following commands: npm install google-closure-compiler and npm install google-closure-library. My application can be successfully co ...

Using TypeOrm QueryBuilder to establish multiple relations with a single table

Thank you for taking the time to read and offer your assistance! I am facing a specific issue with my "Offer" entity where it has multiple relations to "User". The code snippet below illustrates these relationships: @ManyToOne(() => User, (user) => ...

Issue with knockout view - unable to remove item from view after deletion

I'm facing an issue with my code that deletes an exam from a list of exams on a page, but the deleted exam still shows up until the page is manually refreshed. This pattern works correctly on other pages, so I don't understand why it's not w ...

Is there a way to package extra files along with `NodejsFunction` in Node.js?

I am looking to add another HTML file to the source code, like shown below. https://i.sstatic.net/OyxDM.png Here is my current code: const mailerFunction = new aws_lambda_nodejs.NodejsFunction(this, 'ApiNotificationHandler', { runtime: lambd ...

Ignore TypeScript errors when using TSNode with Mocha by forcing the compiler to emit code despite errors and having TSNode execute the emitted code

Below is a code snippet where the function test2 is invalid, but it should not affect testing of function test1: export function test1(): boolean { return true; } export function test2(): number { return "1"; } Test: import { assert as Assert } fr ...

Is the utilization of the React context API in NextJS 13 responsible for triggering a complete app re-render on the client side

When working with NextJS 13, I've learned that providers like those utilized in the React context API can only be displayed in client components. It's also been made clear to me that components within a client component are considered part of the ...

Animating Angular ng-template on open/close state status

I am looking to add animation when the status of my ng-template changes, but I am unable to find any information about this component... This is the code in my report.component.html <ngb-accordion (click)="arrowRotation(i)" (panelChange)="isOpen($even ...

Trouble accessing data property within a method in Vue 3 with Typescript

I am facing an issue with accessing my data in a method I have written. I am getting a Typescript error TS2339 which states that the property does not exist in the type. TS2339: Property 'players' does not exist on type '{ onAddPlayers(): vo ...

The Angular 2 UI is unable to successfully connect with the controller through the API call

When attempting to call a URL from Angular 2 using http.get(), an exception is being thrown and the debugger in the controller is not being hit. Although I have checked the proxy and routing, they are the same as my existing projects. This new project is c ...

Encountering: error TS1128 - Expecting declaration or statement in a ReactJS and TypeScript application

My current code for the new component I created is causing this error to be thrown. Error: Failed to compile ./src/components/Hello.tsx (5,1): error TS1128: Declaration or statement expected. I've reviewed other solutions but haven't pinpointed ...

Exploring the process of selecting checkboxes in Angular 6

I'm currently learning Angular 6 and I have a requirement to mark checkboxes based on specific IDs from two arrays: this.skillArray = [ {ID: 1, name: "Diving"}, {ID: 2, name: "Firefighting"}, {ID: 3, name: "Treatment"}, ...

Refresh a page automatically upon pressing the back button in Angular

I am currently working on an Angular 8 application with over 100 pages (components) that is specifically designed for the Chrome browser. However, I have encountered an issue where the CSS randomly gets distorted when I click the browser's back button ...

Encountering a tuple type TypeScript error with a spread argument is far too frequent an occurrence

Encountering this error is becoming a frequent occurrence for me, and I am currently unable to resolve it. src/graphics.ts:105:55 - error TS2556: A spread argument must either have a tuple type or be passed to a rest parameter. 105 _queue.forEach((_ ...

Unable to successfully import { next } from the 'express' module using Typescript

Having some trouble with this line of code: import {response, request, next} from 'express' The typescript compiler in vscode is giving me the following error: Module '"express"' has no exported member 'next'. Up ...

Utilizing Firebase Cloud Functions to perform querying operations on a collection

Imagine having two main collections: one for users and another for stories. Whenever a user document is updated (specifically the values username or photoUrl), you want to mirror those changes on corresponding documents in the story collection. A sample u ...

Retrieve the Ionic storage item as a string

My issue is related to the code snippet below: this.storage.get('user') Upon execution, it returns the following object: t {__zone_symbol__state: null, __zone_symbol__value: Array(0)} I am uncertain about how to handle this object. Is there ...

Naming convention for TypeScript accessors

Expanding on the previous solution When I convert the example object to JSON from the answer above: JSON.stringify(obj) The output is: {"_id":"3457"} If I intend to transmit this data over a service and store it in a database, I prefer not to use the ...