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

Using TypeScript to Return a Derived Class as a Method's Return Type

I'm currently facing a challenge with an abstract class in typescript that includes a method to provide a callback for later use. The issue lies in the return type of the method, as it is set to the class itself, preventing me from using fluent style ...

Tips for centering an Angular mat prefix next to a label in a form field

Hey everyone, I need some help with aligning the prefix for an input with the mat label. Can anyone suggest a way to adjust the mat prefix so that it lines up perfectly with the mat label? Any assistance or ideas would be greatly appreciated. Here is the ...

Creating personalized breakpoints in Material UI using TypeScript

When using the createMuiTheme() function, you have the ability to update breakpoint values like this. const theme = createMuiTheme({ breakpoints: { values: { xs: 0, sm: 600, md: 960, lg: 1280, xl: 1920, }, }, }) ...

Inability to assign a value to an @input within an Angular project

I recently started using Angular and I'm currently trying to declare an input. Specifically, I need the input to be a number rather than a string in an object within an array. However, I'm encountering difficulties and I can't figure out wha ...

Issues arise with the escape key functionality when attempting to close an Angular modal

I have a component called Escrituracao that handles a client's billing information. It utilizes a mat-table to display all the necessary data. When creating a new bill, a modal window, known as CadastrarLancamentoComponent, is opened: openModalLancame ...

Combining Multiple .ts Files into a Single File: A Simplified Application Structure with TypeScript 1.8

Currently, I am in the process of developing an Electron application and I have decided to implement TypeScript for this project. While TypeScript essentially boils down to JavaScript in the end, my familiarity with it makes the transition seamless. As of ...

Separate string by using a regular expression pattern

Looking to parse a dynamic string with varying combinations of Code, Name, and EffectDate. It could be in the format below with all three properties or just pairs like Code-Name, Code-EffectDate, or Name-EffectDate. {"Code":{"value":"1"},"Name":{"value": ...

The webpack development server refreshes the page just one time

I've been searching through various issues on Stack Overflow but haven't had any luck. It seems like most solutions are for an older version of webpack-dev-server. Despite trying numerous things, my app just won't reload or rebuild more tha ...

Displaying all notifications while using parameters in TypeScript

I am trying to display all of my notifications in HTML. The value is returned in res = response.json();, but my website only shows one notification, similar to the example in Let's start with this code: public eventsbyserial(id: string): Observab ...

Output specification: Mandate certain attributes of a designated kind, while permitting them to be incomplete

I am searching for a solution in TypeScript that enforces the return type of a method to be a subset of an interface. Essentially, this means that all properties on the returned object must exist on the interface, but they are not required. Background: De ...

Sign out from Azure Active Directory using the ADAL library in an Angular application written in TypeScript

When navigating multiple tabs in a browser, I am encountering an issue with logging out using adal. How can I successfully log out from one tab while still being able to use another tab without any hindrance? Currently, when I log out from one tab, it pr ...

Processing HTTP requests routed from Firebase Hosting to Cloud Functions

I'm currently working on integrating data patching with my Firestore database using http. My goal is to achieve this without relying on an external server, by utilizing Firebase Hosting and Functions. Firstly, I set up my Firebase project and importe ...

What is the reason why modifying a nested array within an object does not cause the child component to re-render?

Within my React app, there is a page that displays a list of item cards, each being a separate component. On each item card, there is a table generated from the nested array objects of the item. However, when I add an element to the nested array within an ...

Tips for utilizing the value of object1.property as a property for object2

Within the template of my angular component, I am attempting to accomplish the following: <div> {{object1.some_property.(get value from object2.property and use it here, as it is a property of object1)}} </div> Is there a way to achieve this ...

While utilizing Ionic to upload images to a server, I encountered the FileTransferError error code 3

I have successfully uploaded an image from the gallery, but I am facing issues while uploading multiple images at once. Here is the code snippet I am using: pictureUpload(x){ // x represents the file path like - file:///storage/emulated/0/Download/palak ...

Different TypeScript parameters that cannot be used together

Consider the given JavaScript function below: function x({foo, fooId, bar, barId}) {} I am looking to refactor this function into TypeScript in such a way that the caller is required to provide either foo or fooId, but not both. The same rule should apply ...

Issue with deploying lodash library

I'm encountering an issue with lodash. Whenever I deploy using gulp, I consistently receive the following error: vendors.min.js:3 GET http://127.0.0.1/projects/myproject/lodash 404 (Not Found) I have declared the library in my index.html file < ...

Obtain unfinished designs from resolver using GraphQL Code Generator

In order to allow resolvers to return partial data and have other resolvers complete the missing fields, I follow this convention: type UserExtra { name: String! } type User { id: ID! email: String! extra: UserExtra! } type Query { user(id: ID! ...

Promise disregards the window being open

I'm facing an issue with redirecting users to Twitter using window.open in a specific function. It seems like the instruction is being ignored, even though it works perfectly on other pages. Any ideas on how to fix this? answerQuestion() { if ...

Issue: Module '/Users/MYNAME/Desktop/Projects/MYPROJECTNAME' not found

I am currently in the process of compiling Node using TypeScript, and I'm still getting acquainted with it. An issue I encountered was that my /src files were not being updated when I made changes and restarted the server. To troubleshoot, I decided ...