Is it possible for Typescript to automatically determine the exact sub-type of a tagged union by looking at a specific tag value?

I have an instance of type Foo, which contains a property bar: Bar. The structure of the Bar is as follows:

type ABar = {name: 'A', aData: string};
type BBar = {name: 'B', bData: string};
type Bar = ABar | BBar;
type BarName = Bar['name']; // this represents 'A' or 'B'

Various sections of my code need to handle foo.bar based on its specific type; some parts only require bar to be an ABar, while others need it to be a BBar. I want to create a function that safely extracts the Bar from a Foo object. Here's the function I currently have:

function getBar(foo: Foo, name: BarName): Bar | null {
    return foo.bar.name === name ? foo.bar : null;
}

While this function works, the caller needs to explicitly assert the correct type like so:

let aBar = getBar(foo, 'A') as ABar | null;

Is there a way for TypeScript to infer that if the input name is 'A', then the output must be of type ABar (or null)?

Check out the example in TS Playground.

Answer №1

To create a more versatile function, you can make it generic and leverage the Extract utility type to extract a specific member from a union based on a provided name.

function fetchData<T extends DataType>(input: DataInput, name: T) {
    return (
      input.data.name === name ? input.data : null
    ) as Extract<DataType, { name: T }> | null;
}

let dataOne = fetchData(input, 'One');
// let dataOne: TypeOne | null

let dataTwo = fetchData(input, 'Two');
// let dataTwo: TypeTwo | null

Playground

Answer №2

Initially, it is important to mention that TypeScript has the ability to deduce the correct type of Bar when checking the name property within an if statement:

if (bar.name === "A") {
  bar.aData; // No error, as 'ABar' type is assigned to 'bar' here.
}

This approach may suffice based on your code structure. However, if you still wish to utilize the getBar function, there are alternative methods you can explore:


The first option involves using a different type that links each specific Bar type with its corresponding name. For example:

type BarMap = {
  A: ABar;
  B: BBar;
}

By employing a generic function, looking up the correct associated type based on the name becomes straightforward:

function getBar<T extends BarName>(foo: Foo, name: T): BarMap[T] {
  // @ts-ignore: Despite TypeScript's complaint, this solution works effectively.
  return foo.bar.name === name ? foo.bar : null;
}

Although an error message may appear on the return line, overlooking it should not be problematic since functionality remains intact at runtime with accurate inference:

let aBar = getBar(foo, 'A'); // Type: ABar | null
let bBar = getBar(foo, 'B'); // Type: BBar | null

If an invalid name from the BarName list is provided, an error will be triggered:

let cBar = getBar(foo, 'C') // Error: Argument of type '"C"' is not assignable to parameter of type '"A" | "B"'

Alternatively, the second option consists of incorporating overload signatures in the function, enabling explicit definition of the return type for each combination of parameters:

function getBar(foo: Foo, name: 'A'): ABar | null;
function getBar(foo: Foo, name: 'B'): BBar | null;
function getBar(foo: Foo, name: BarName): Bar | null {
  return foo.bar.name === name ? foo.bar : null;
}

This method achieves the same inference and generates an error upon receiving an unauthorized name. Additional overload signatures could also be included to specify a bare null return type for unknown keys.

Selecting between these solutions largely depends on the maintainability aspect tailored to your specific use case.


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

Cross-origin request error persists despite configuring headers on the server. Unable to successfully relocate image to designated directory on the server

I am encountering a CORS error specifically when sending delete requests from Angular to Laravel. Additionally, I am facing issues with moving car model images to the directory during posting, resulting in errors. I have implemented a CORS middleware and a ...

What's the most effective method for transferring data to different components?

How can I efficiently pass a user info object to all low-level components, even if they are grandchildren? Would using @input work or is there another method to achieve this? Here is the code for my root component: constructor(private _state: GlobalSta ...

Converting and downloading CSV to XLSX directly from the front end using TypeScript and React

After successfully converting a JSON response to CSV format for download using the function below, I am now looking to achieve the same functionality but with xlsx files on the front end. The current function works well for CSV files and handles Japanese ...

Expo + tRPC: Oops! Looks like the application context couldn't be retrieved. Don't forget to wrap your App inside the `withTRPC` HoC for

I'm currently working on a straightforward tRPC server setup: // server.ts import { initTRPC } from "@trpc/server"; import { z } from "zod"; const t = initTRPC.create(); export const appRouter = t.router({ greeting: t.procedu ...

Convert JSON data to an array using Observable

My current task involves parsing JSON Data from an API and organizing it into separate arrays. The data is structured as follows: [ {"MONTH":9,"YEAR":2015,"SUMAMT":0}, {"MONTH":10,"YEAR":2015,"SUMAMT":11446.5}, {"MONTH":11,"YEAR":2015,"SUMAMT":5392 ...

Error: The function was expecting a mapDiv with the type of Element, but instead undefined was passed - google

I have a map within a div tagged with #mapa. Whenever I try to plot a route on the map, it refreshes. I don't want the map to refresh, and here is the code I currently have: <div style="height: 500px; width: auto;" #mapa> <google-map heigh ...

How to effectively handle null in Typescript when accessing types with index signatures unsafely

Why am I getting an error that test might be potentially undefined even though I've enabled strictNullCheck in my tsconfig.json file? (I'm unsure of the keys beforehand) const a: Record<string, {value: string}> = {} a["test"].va ...

Unveiling RxJs: The secret to extracting the notifier value using the takeuntil operator

I have a straightforward Rxjs timer set up that runs until a notifier emits a signal, it's pretty basic so far. enum TimerResult = { COMPLETE, ABORTED, SKIPPED }; _notifier: Subject<TimerResult> = new Subject(); notifier$: Observab ...

Vue: Simple ways to retrieve state data in MutationAction

I'm having trouble accessing the state inside @MutationAction Here is the setup I am using: Nuxt.js v2.13.3 "vuex-module-decorators": "^0.17.0" import { Module, VuexModule, MutationAction } from 'vuex-module-decorators' ...

Displayed even when data is present, the PrimeNg empty message persists

I have set up a PrimeNg table to display data with an empty message template like this: <ng-template pTemplate="emptymessage"> <tr> <td> No records found </td> </tr> </ng-template> ...

Prisma Remix is throwing a TypeError: "The function (0, import_prisma.createNote) is not defined as a function."

In my project, I wrote a function using the prisma client which is being called from the notes.tsx route in remix. export async function createNote(entity: { title: string, description: string }) { const note = await prisma.note.create({ data: ...

Tips on utilizing storage.set() within google.maps.events.addListener(marker, 'dragend', function() { }); in Ionic 3

google.maps.event.addListener(Marker, 'click', (function(Marker) { return function() { this.storage.set('mylocation', this.Marker.getPosition()); } })(Marker)); polyfills.js:3 Uncaught TypeError: Cannot read property 'set ...

Error message stating: rxjs and firebase encountered a TypeError when attempting to add property 0 because the object is not

My angular application interacts with firebase firestore as the backend database. I am working on a function to retrieve document snapshots from firestore in a generic way. Here is the code snippet where I encounter an error: /** * Get a 'liste ...

Issue: Angular is indicating that the 'feedbackFormDirective' member is implicitly assigned with type 'any'

I am encountering an error in my project while using Angular version 12. Despite extensive research, I have been unable to find a solution. Here is my .ts file: import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Feedba ...

Http' does not have the 'update' property

I recently implemented Angular 2 Release and utilized 'Http' from '@angular/http' for my project. However, I encountered an error when I invoked the method 'update', which resulted in the following message: "Evidently, th ...

Tips for updating property values when calling a TypeScript function

Hello everyone, I am looking to convert a snippet of JavaScript code into TypeScript. JavaScript function newState(name){ var state ={ name : name, age : 0 } return state } function initStates() { this.JamesStat ...

Using NodeJS API gateway to transfer image files to S3 storage

I have been attempting to upload an image file to S3 through API Gateway. The process involves a POST method where the body accepts the image file using form-data. I crafted the lambda function in TypeScript utilizing the lambda-multipart-parser. While it ...

Could this type declaration in the Vue decorator constructor be accurate?

When using Vue decorator notation, I typically write it like this: @Prop({ type: Object || null, default: null }) However, I noticed in the Vue documentation that they use array notation: @Prop({ type: [ Object, null ], default: null }) Is there a specif ...

When attempting to utilize class validators in NestJS, Param is refusing to cast to DTO type

I'm currently working on implementing validation for the parameter I receive in a request, especially when trying to delete something. The parameter is expected to be a string but it must adhere to the format of a valid UUID. To achieve this, I have i ...

Exploring the possibilities of ZMQ_XPUB_MANUAL in action with zeromq.js

I'm currently in the process of setting up a pub/sub broker using ZeroMQ, and I want to ensure that clients are only able to subscribe to authorized prefixes. While researching this topic, I came across a helpful tutorial that discusses achieving a si ...