Is it possible to assign a type conditionally depending on the value of a boolean?

While grappling with this issue, the title question arose in my mind: How can I handle the situation where the library function getResponse returns { a: number } for Android phones and { b: number } for iOS phones?

The code snippet below initially led to a type error when assigning a value to value:

const response = getResponse();

const value = isAndroid ? response.a : response.b;

To address this, I implemented a type guard by defining a type predicate named isAndroidResponse

const isAndroidResponse = (
  response: { a: number } | { b: number }
): response is { a: number } => {
  return isAndroid; // 'response' parameter not used!
};

This type guard was then utilized as follows:

const response = getResponse();

let value;
if (isAndroidResponse(response)) {
  value = response.a;
} else {
  value = response.b;
}

Nevertheless, two concerns emerged regarding this solution, causing me to question its effectiveness for my problem:

  1. The type predicate does not utilize the input response variable.

  2. The implementation entails a considerable amount of code. Ideally, I would prefer a more concise approach, such as the following (which is invalid TypeScript syntax):

    type ResponseT = isAndroid ? { a: number } : { b: number };
    
    const response = getResponse();
    
    const value = isAndroid ? response.a : response.b;
    

This leads me to my follow-up inquiry: Is leveraging a type predicate a viable solution, or am I misapplying it in this context?

I have also set up a TS playground showcasing the scenario described above.

Answer №1

In a conceptual sense, there exists a boolean variable isAndroid that works as a distinguishing factor for the union type derived from the getResponse() function. Essentially, this means that the return type of getResponse() resembles a discriminated union; however, in this case, the discriminant property is somewhat detached from the actual return value itself. This situation can be referred to as a "nonlocal discriminated union," in absence of an established term.

TypeScript is not inherently equipped to comprehend nonlocal discriminated unions, thereby necessitating some form of workaround.


If your goal is to minimize redundant code, the most viable option would be to employ type assertions to explicitly inform the compiler about what it cannot ascertain on its own:

const response = getResponse();
const value = isAndroid ? 
  (response as AndroidResponse).a : 
  (response as IOSResponse).b;

This approach may lack type safety since there are no constraints against altering the condition from isAndroid to

!isAndroid</code); similarly, the implementation of your custom <code>isAndroidResponse()
type guard function isn't entirely foolproof. In scenarios where getResponse() appears infrequently in your codebase, type assertions could be deemed acceptable.


Conversely, if you anticipate utilizing getResponse() across numerous sections of your code, it might be prudent to refactor such that the unsafe segment is encapsulated within a singular function, like isAndroidResponse().

Should you opt for this course of action, my personal opinion (albeit subjective) is that your current isAndroidResponse() implementation suffices. Though unconventional due to performing actions without analyzing the response argument, proper documentation could mitigate any potential confusion.


Another strategy involves transforming the nonlocal discriminated union into a genuine discriminated union by incorporating isAndroid as a discriminant property within the response:

// Type definitions
type Response =
    ({ isAndroid: true } & AndroidResponse) |
    ({ isAndroid: false } & IOSResponse)
const getResponseʹ = () =>
    Object.assign(getResponse(), { isAndroid }) as Response

// Utilization
const response = getResponseʹ();
const value = response.isAndroid ? response.a : response.b;

By confining the unsafe code to the confines of the getResponseʹ() function and asserting the return type, this method allows you to relinquish reliance on the original isAndroid flag while leveraging the language's support for discriminated unions.


Link to Playground with Code

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

Enable or disable dropdown based on selected radio button status

I need the dropdown to be disabled unless the radio button labeled prov is selected. It should only be enabled when prov is chosen. I attempted to create a demo, but it's not functioning correctly and I can't figure out why. HTML: <td colsp ...

What are the advantages of going the extra mile to ensure cross-browser compatibility?

It's fascinating how much effort is required to ensure web applications work seamlessly across all browsers. Despite global standards like those set by W3C, the development and testing process can be quite laborious. I'm curious why all browsers ...

What location is ideal for making API calls with React and Redux-thunk?

After extensively searching on StackOverflow.com and across the internet, I couldn't find a similar question. Therefore, please refrain from giving negative reputations if you happen to come across one as I truly need reputation at this point in my li ...

MVC3: Easily browse through tables by accessing details directly from the details view, eliminating the need to click on

I am currently working on an Index view where a table displays a list of items, each with a link to show its details in another partialview loaded through Ajax. Users have expressed interest in being able to easily navigate between "Next Item" and "Previo ...

Unresolved issue with AngularJS radio button binding

As a beginner in using Angular.js, I encountered an issue with data binding when dealing with radio buttons. The HTML code in question is: <label class="options_box" ng-repeat="item in item_config_list.item_config"> <input type="radio" name ...

Unexpected reduce output displayed by Vuex

In my Vuex store, I have two getters that calculate the itemCount and totalPrice like this: getters: { itemCount: state => state.lines.reduce((total,line)=> total + line.quantity,0), totalPrice: state => state.lines.reduce((total,line) = ...

How to stop Accordion from automatically collapsing when clicking on AccordionDetails in Material-UI

I am working on a React web project with two identical menus. To achieve this, I created a component for the menu and rendered it twice in the App component. For the menu design, I opted to use the Material UI Accordion. However, I encountered an issue wh ...

Emphasize x-axis heading in a Highcharts graph

In my Highcharts bar graph, I am looking for a way to dynamically highlight the x-axis label of a specific bar based on an external event trigger. This event is not a standard click interaction within the Highcharts graph. Currently, I have been able to r ...

Detecting changes in a readonly input in Angular 4

Here is a code snippet where I have a readonly input field. I am attempting to change the value of this readonly input from a TypeScript file, however, I am encountering difficulty in detecting any changes from any function. See the example below: <inp ...

Expanding and collapsing DIV elements using JavaScript upon clicking navigation menu items

At present, the current code unfolds the DIVs whenever a user clicks on a menu item. This results in the DIV folding and unfolding the same number of times if clicked repeatedly on the same link, without staying closed. My desired functionality is to have ...

How do I create a new JSON Object with only two properties by selecting from an existing JSON Object with four properties?

Below is a JSON object example: [ {'car':'punto','brand':'fiat','color':'black','model':'2007'}, {'car':'XUV500','brand':&ap ...

Using the express.Router instance for handling errors can be a useful tool in your development

Based on the documentation, it states that any nodejs express middleware function has the capability of being swapped out by App or Router instances: Given that router and app adhere to the middleware interface, they can be used just like any other midd ...

Adding Currency Symbol to Tooltip Data in Material-UI Sparklinechart

Below is a SparklineChart example using MUI library: import * as React from 'react'; import Stack from '@mui/material/Stack'; import Box from '@mui/material/Box'; import { SparkLineChart } from '@mui/x-charts/SparkLineCha ...

Utilizing a powerful combination of Angular 5, PrimeNG charts, Spring Boot, and JHipster

I am facing an issue with creating charts using PrimeNG. The main challenge I'm encountering is the conversion of data from a REST API in Angular 5 (TypeScript) and retrieving the list of measurements from the API. I have an endpoint that returns my m ...

The camera feature in Ionic Cordova seems to be malfunctioning

I am attempting to implement the ionic cordova camera feature. Here is the code snippet I have: HomePage.html <ion-view view-title="Example"> <ion-content> <img ng-show="imgURI !== undefined" ng-src="{{imgURI}}"> <img ng-s ...

Who is the intended audience for the "engines" field in an npm package - consumers or developers?

As the creator of an npm library, I have included the current LTS versions of Node.js and npm in the package manifest under the engines field. This ensures that all contributors use the same versions I utilized for development: Node.js <a href="/cdn-cgi ...

Height of the div dynamically increases upwards

Just a quick question - is there a way to make position: fixed divs at the bottom of an element (such as the body) grow upwards as content is dynamically added? Maybe something like anchor: bottom or increase: up? I'm thinking that using JavaScript m ...

Having trouble converting JSON into a JavaScript object

I am encountering an issue with my HTML box: <span>Select department</span><span> <select id="department" onchange="EnableSlaveSelectBox(this)" data-slaveelaments='{"a": 1, "b": "2& ...

Deactivate the button once it's been used | Discord.js version 14

In my index.js, I created a function to respond when a button is pressed and then disable it. client.on("interactionCreate", async (interaction) => { if (interaction.isButton()) { if (interaction.customId === "yes") { co ...

Transform uploaded image file into a blob format and store it in a VueJS database

I am facing an issue with my form that has multiple inputs, including one of type "file". My goal is to upload an image and then submit the form to the API for storage in the database. <input name="image" class="w-full border-2 border-gray-200 rounded-3 ...