Understanding the connection between two unions through TypeScript: expressing function return types

Within my codebase, I have two unions, A and B, each with a shared unique identifier referred to as key.

The purpose of Union A is to serve as input for the function called foo, whereas Union B represents the result yielded by executing the function foo.

In terms of functionality, it can be guaranteed that foo will select an element from Union B that has the same key as the provided Union A.

My goal here is to convey this particular characteristic within the return type definition of foo.

To better illustrate what I mean, please refer to the following snippet:

type UnionA =
  | {
      key: "one";
    }
  | {
      key: "two";
    };

type UnionB =
  | {
      key: "one";
      value: boolean;
    }
  | {
      key: "two";
      value: string;
    };

const b: UnionB[] = [
  { key: "two", value: "ok" },
  { key: "one", value: true },
];

function foo(val: UnionA): UnionB | undefined {
  for (let i = 0; i < b.length; i++) {
    if (val.key === b[i].key) {
      return b[i];
    }
  }

  return undefined;
}

const c = foo({ key: "two" });

// The below produces an error:
// Property 'length' does not exist on type 'string | boolean'
//
// But it's actually guaranteed to be a string
console.log(c && c.value.length);

My question now is whether it is feasible to inform TypeScript that the output type of foo relies on the value specified in the key attribute of the input parameter?

Answer №1

When dealing with a function that maps from UnionA to UnionB, and both unions have a shared key property to signify their relationship, we can create a generic input and extract the output using the Extract utility type:

function foo<T extends UnionA>(val: T): Extract<UnionB, { key: T["key"] }> | undefined {
    for (let i = 0; i < b.length; i++) {
        if (val.key === b[i].key) {
            return b[i] as Extract<UnionB, { key: T["key"] }>; // unresolved generic, cast
        }
    }
    return undefined;
}

const c = foo({ key: "two" }); // { key: "two"; value: string; } | undefined
console.log(c && c.value); // "ok"

Explore this example further

Answer №2

Perhaps I can suggest an alternate solution that may suit your specific situation, although it does not directly address your query. You could approach the issue by creating UnionA from UnionB in this manner:

type UnionB = {
      key: "one";
      value: boolean;
    }
  | {
      key: "two";
      value: string;
};
type Key = UnionB["key"];
type UnionA = Record<"key", Key>

For a demonstration, you can experiment with this code on this playground.

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

Tips for utilizing express in your typescript projects

I'm curious about the transition of definition files from tsd to typings, and now to @types. How can I incorporate @types in a node/express project? What is currently preferred and what's the reason for moving from tsd to typing and finally to @t ...

Leveraging IF conditions on part of the worksheet title

Currently, my script is performing the task of hiding three columns for tabs in a workbook that start with "TRI". However, the execution speed is quite sluggish. I am seeking suggestions on how to optimize and enhance the performance. If possible, please p ...

Enhancing a prototype instance in TypeScript while activating strict mode

When working with an instance named remote from a factory in a vendor script, I encountered the need to add my own methods and members to that instance. While seeking a solution, I came across an insightful response on extending this in a Typescript class ...

The 'type' property is not present in the 'ChartComponent' type, however, it is necessary in the 'ApexChart' type

Encountered an error highlighted in the title: Property 'type' is missing in type 'ChartComponent' but required in type 'ApexChart'. Any attempt to resolve this issue led to another error message: Type '{ type: string; ...

Angular: Utilizing the new HttpClient to map HTTP responses

Within my application, I have a standard API service that communicates with the backend using requests structured like this: post<T>(url: string, jsonObject: object): Observable<T> { return this.http.post<T>(url, JSON.stringify(json ...

What is the best way for me to use a ternary operator within this code snippet?

I'm in the process of implementing a ternary operator into this snippet of code, with the intention of adding another component if the condition is false. This method is unfamiliar to me, as I've never utilized a ternary operator within blocks of ...

Why won't my code work with a jQuery selector?

I'm struggling to retrieve the value from a dynamically generated <div> using jQuery. It seems like the syntax I'm using doesn't recognize the div with an id tag. The HTML code is stored in a variable, and below is a snippet of code w ...

What is the best approach to creating an array within my formgroup and adding data to it?

I have a function in my ngOnInit that creates a formgroup: ngOnInit() { //When the component starts, create the form group this.variacaoForm = this.fb.group({ variacoes: this.fb.array([this.createFormGroup()]) }); createFormGroup() ...

Ways to prevent encountering the "ERROR: Spec method lacks expectations" message despite achieving success

My Angular HTTP service is responsible for making various HTTP calls. Below is a snippet of the service implementation: import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable() expor ...

What is the best way to time a Google Cloud Function to execute at the exact second?

In my attempt to schedule a cloud function using the Pub/Sub trigger method along with crontabs, I realized that it only provides granularity to the nearest minute. However, for my specific application - which involves working with trades at precise time ...

Resolve ESLint errors in _document.tsx file of next.js caused by Document<any> and ctx.renderPage = () with TypeScript usage

maxbause took the initiative to create a comprehensive boilerplate project for Next.js, complete with GraphQL and styled components in TypeScript. Check out the project here However, upon integrating ESLint into the project, I encountered several warning ...

Error message shows explicit Typescript type instead of using generic type name

I am looking to use a more explicit name such as userId instead of the type number in my error message for error types. export const primaryKey: PrimaryKey = `CONSUMPTION#123a4`; // The error 'Type ""CONSUMPTION#123a4"" is not assignable to ...

Experimenting with a function that initiates the downloading of a file using jest

I'm currently trying to test a function using the JEST library (I also have enzyme in my project), but I've hit a wall. To summarize, this function is used to export data that has been prepared beforehand. I manipulate some data and then pass it ...

Tips for saving the generated POST request ID for future use in another function

I am facing a challenge where I want to use the ID of a newly created Order as the OrderId for an OrderLine that needs to be created immediately after. After creating the Order, if I log the orderId INSIDE THE SUBSCRIBE METHOD, it shows the correct value. ...

Why is it that in reactive forms of Angular, the parameter being passed in formControlName is passed as a string?

I am currently working on a reactive form in Angular. In order to synchronize the FormControl object from the TypeScript file with the form control in the HTML file, you need to utilize the formControlName directive. This is accomplished as shown below: f ...

Looking for a solution to the TypeScript & Mantine issue of DateValue not being assignable?

The required project dependencies for this task are outlined below: "dependencies": { "@mantine/core": "^7.6.2", "@mantine/dates": "^7.6.2", "@mantine/form": "^7.6.2", &q ...

Storing data locally in Angular applications within the client-side environment

As I delve into Angular and TypeScript, I've encountered a perplexing issue. Let's say I have two classes - Employee and Department. On the server-side, I've established a Many-To-One relationship between these entities using Sequelize: db. ...

I am completely baffled by the meaning of this Typescript error

In my code, there is a button component setup like this: export interface ButtonProps { kind?: 'normal' | 'flat' | 'primary'; negative?: boolean; size?: 'small' | 'big'; spinner?: boolean; ...

Is there a way to signal an error within an Observable function that can handle multiple scenarios depending on the specific page being viewed in an Angular application?

Currently, I have a function called getElementList() which returns Observable<Element[]>. The goal is to handle different scenarios based on the user's current page - two cases for two specific pages and one error case. However, I am struggling ...

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 ...