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

Issues with the orderBy function are causing unexpected behavior

After creating 3 groups - priority 1, 2, and 3 with orderBy:priorty for each pushed task to go into their designated group, I noticed some strange behavior within the groups where they don't sort properly when more data is added. <input ng-model=" ...

Tips for preventing the use of website URLs as variables in jQuery ajax() calls:

Having some trouble. For example: $(document).ready(function () { $("#btnSend").click(function () { var noti_p = { url: "Pusher_Controller.ashx", data: "&appname=" + $("#selt1").val() + "&title=" + $("#title"). ...

Issue with Angular 7 Universal: components inside are failing to display

I have successfully implemented Angular 7 Universal with dynamic server-side rendering. However, I am facing an issue where dynamic components within the main component being rendered on the server are not rendered themselves. Here is an example of the re ...

Dynamic display without AJAX

I need assistance with two drop-down lists where the second list's values should change based on the selection made in the first list. The current set up of the drop-down lists is as follows: <select name="first"> <option name="a" va ...

What is the process for obtaining the URL of the website that is hosting my iframe?

Do you have a technical inquiry? I am curious to know if it is feasible to retrieve the URL of the website that is hosting my iframe. The pages that host my iframe are utilizing the following code: <iframe id="vacancy-iframe" src="http://mypage.co ...

Incorporate a unique attribute into a select tag using Javascript

My goal is to dynamically generate a select element using JavaScript and include a custom attribute called data-placeholder. Is there a way to add non-standard attributes using JavaScript without triggering a browser error like: Uncaught referenceError: ...

Capable of retrieving information from an API, yet unable to display it accurately within the table structure

I'm currently working with Angular version 13.2.6 and a .NET Core API. I have two components, PaymentdetailsView (parent) and PaymentDetailsForm (child). Within the PaymentDetailsForm component, there is a form that, when submitted, makes a call to ...

Easily validate the contenteditable attribute of <td> element

I am currently working on a table that displays data from a MYSQL database. Whenever a user makes changes to an element in the table, I want the database to be updated using AJAX. Below is my JavaScript code for sending data in an editable row. function s ...

Explore the power of accessing XML data using JavaScript

Currently, I am dealing with an XML file and attempting to extract data from it. The structure of the XML file is as follows: <doc> <str name="name">Rocky</str> <str name="Last_name">balboa</str> <str name="age ...

Extending a type by adding properties from separate files using TypeScript

I am faced with a situation where I have a file containing either a type or interface variable (all of which are exported), and I need to add new properties to this variable from different files without using extends. Essentially, making changes to the sam ...

What are the best practices for incorporating jQuery animations in Angular directives?

For my website, I crafted a straightforward directive aimed at adding some basic animations to the sidebar. The animations include smooth sliding in and adjusting the width and margin of the content class. My query revolves around the suitability of emplo ...

What is the best way to interpret the data from forkjoin map?

As a newcomer to angular and rxjs, I am seeking guidance on how to properly retrieve data from forkJoin using a map function. ngOnInit(): void { this.serviceData.currentService.subscribe(service => this.serviceFam.getAllFamilles().pipe( ...

The application of knockoutjs bindings is restricted to specific ids and cannot be used on any

In my project, I have multiple views where each one applies bindings to its own tag individually. Here is a snippet of the code: (Please note that some code has been omitted for brevity. For a more complete example, you can view the full fiddle here: http ...

There were no visible outputs displayed within the table's tbody section

import React from 'react'; export default class HelloWorld extends React.Component { public render(): JSX.Element { let elements = [{"id":1,"isActive":true,"object":"Communication","previ ...

Exporting third party definitions from a TypeScript npm module for reuse in other projects

Our custom npm logging module, which is built using TypeScript and relies on the pino library, encounters errors when being imported into an application: Error: node_modules/@scope/logging/lib/index.d.ts(1,23): error TS2688: 'pino' type definiti ...

Numerous volume sliders catering to individual audio players

I'm currently working on implementing volume sliders for multiple audio players, similar to what you can find on . With the help of Deepak, I've managed to create code that allows me to control play/pause and adjust the volume of various audio pl ...

Creating a JSON schema in JavaScript using an already established JSON framework

I have a json structure stored in a variable called "data" that looks like this: { "SearchWithMasterDataDIdAndScandefinitionDAO": [ { "dateDm_id": 20120602, "issueValue": "ELTDIWKZ", "scanName": "Company Stored as Person (Give ...

Using local variables in NodeJS callbacks

Despite the abundance of information on SO, I have yet to find a suitable answer to my particular situation. The following code snippet is causing me issues: for(var i=0; i < shop.collections.length; i++){ if(!shop.collection[i].active){ var dat ...

Combine the information from another cell and add it to the current cell with additional data

Can you help me figure out a way to input numbers and have Google Sheets calculate the change percentage in the same cell? Here's an example: Oct 20 Nov 20 Dec 20 90 100 (+10%) 95 (-5%) I'm hoping to do this without using additional cell ...

Using jQuery to determine if the child element is a <ul> tag

HTML: <ul class="menu"> <li><a href="http://example.com">Text</a> <ul> <li><a href="http://example.com">Text</a> <li><a href="#">Text</a> <li><a href="# ...