What is the best way to handle optional object properties within Typescript union types?

Consider this scenario:

type GeneralAPIError = {
    status: "error";
    message: string;
  };
  
  type Foo = {
      status: string;
      whatever: string;
  }
  
  function wat(): Foo | GeneralAPIError {
      return Math.random() > .5 ? {
          status: "error",
          message: "noooo"
      } : {
          status: "success",
          whatever: "yayyy"
      };
  }
  
  const eh = wat();
  
  // This doesn't work
  console.log(eh.whatever || eh);
  
  // Neither does this
  if(eh.hasOwnProperty("whatever")) {
      console.log(eh.whatever)
  } else {
      console.log(eh);
  }
  
  // This does, but the "as" is pretty ugly
  if(eh.hasOwnProperty("whatever")) {
      console.log((eh as Foo).whatever);
  } else {
      console.log(eh);
  }
  
  

What is the optimal way to define the return type of wat() ? In reality, it makes an API request that might result in error JSON (type GeneralAPIError) or success JSON (type Foo). The same error type is used for many similar functions.

The code towards the end that checks for a specific key within an object works correctly, but TypeScript raises some errors:

Property 'whatever' does not exist on type 'GeneralAPIError | Foo'. Property 'whatever' does not exist on type 'GeneralAPIError'.

In the first two examples. It seems like there should be a way to type the function return so that eh.whatever || eh functions properly. What am I overlooking?

Answer №1

To accurately determine the type, utilizing a user-defined type guard is essential.

type GeneralAPIError = {
    status: "error";
    message: string;
};

type Foo = {
    status: string;
    whatever: string;
}

/**
* The purpose of this type guard is to validate if the element belongs to type Foo.
*/
function isFoo(wat: Foo | GeneralAPIError): wat is Foo {
  return (wat as Foo).whatever !== undefined;
}

function wat(): Foo | GeneralAPIError {
    return Math.random() > .5 ? {
        status: "error",
        message: "noooo"
    } : {
        status: "success",
        whatever: "yayyy"
    };
}

const eh = wat();
console.log(isFoo(eh) ? eh.whatever : eh);

If dealing with a straightforward scenario, an alternative method is using the in operator:

type GeneralAPIError = {
    status: "error";
    message: string;
};

type Foo = {
    status: string;
    whatever: string;
}

function wat(): Foo | GeneralAPIError {
    return Math.random() > .5 ? {
        status: "error",
        message: "noooo"
    } : {
        status: "success",
        whatever: "yayyy"
    };
}

const eh = wat();
console.log("whatever" in eh ? eh.whatever : eh);

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

Only implement valueChanges on the second step of the form

I am utilizing the mat-stepper with a single form. My stepper has two steps only. I am looking to make an API request for every input value change, but only when the user is on the second step. How can I accomplish this? .ts ngOnInit() { this.formGr ...

Insert a symbol at the beginning of the Autocomplete component in Material-UI

I am looking to enhance the Autocomplete component by adding an icon at the beginning using startAdornment. I discovered that Autocomplete functions as a regular text input. My attempt so far involved inserting: InputProps={{startAdornment: <InputAdorn ...

Which data structure is suitable for storing a JSON object?

Currently, I have a form set up in the following way: using (Ajax.BeginRouteForm( ... new AjaxOptions { HttpMethod = "POST", OnFailure = "OnFailure", OnSuccess ...

Enabling strict functions type causes an error when using destructuring with combineLatest

After enabling the strictFunctionTypes check in my Angular 11.1.1 project, I encountered an issue with a function that uses RxJs combineLatest. The type passed to the subscription remains an array and is not automatically destructured into individual varia ...

Develop a universal function for inserting information into an array within a data set

I need assistance with my Typescript code. I am currently working on a method that pushes data into an array in a mongoose collection. However, the issue I'm facing is that the value is not being passed dynamically to the Key field in the $set operato ...

Angular will move the input field cursor to the beginning after typing 4 digits

I'm looking for some guidance with a specific scenario involving a reactive form input field. The field is meant to capture the last four digits of a Social Security Number (SSN), and upon filling it out, an API call is triggered to validate the enter ...

Implementing Entity addition to a Data Source post initialization in TypeORM

The original entity is defined as shown below: import { Entity, PrimaryGeneratedColumn} from "typeorm" @Entity() export class Product { @PrimaryGeneratedColumn() id: number The DataSource is initialized with the following code: import ...

What is the best way to output the leaf nodes from an array of object lists in TypeScript?

Having trouble with TypeScript, specifically working with arrays and filtering out leaf nodes. I want to print only the leaf nodes in the array, resulting in ['002', '004', '007']. Can someone please assist me? Excited to lear ...

Discovering duplicates for properties within an array of objects in React.js and assigning a sequential number to that specific field

I am working with an array of objects where each object contains information like this: const myArr=[{name:"john",id:1}{name:"john",id:2}{name:"mary",id:3}] In the first 2 elements, the "name" property has duplicates with the value "john". How can I updat ...

Problem with (click) event not triggering in innerHtml content in Angular 4

For some reason, my function isn't triggered when I click the <a... tag. Inside my component, I have the following code: public htmlstr: string; public idUser:number; this.idUser = 1; this.htmlstr = `<a (click)="delete(idUser)">${idUser}&l ...

Leverage an external JavaScript library within your Angular 8 project

Looking to create a funnel-graph in Angular using the amazing funnel-graph-js library, but facing some challenges in making it work correctly. Below is my funnel-graph-directive.ts import { Directive, ElementRef } from '@angular/core'; // impo ...

What is the reason behind permitting void functions in the left part of an assignment in Typescript?

Take a look at this Typescript snippet: let action = function (): void { //perform actions }; let result = action(); What makes it suitable for the TypeScript compiler? ...

How can I implement a solution that allows for both ref callbacks and MutableRefObject types to be

Trying to enable the use of both types (ref: any) => void Or MutableRefObject<any> interface RowProps extends React.HTMLAttributes<React.ReactHTML> { componentRef: (ref: any) => void | React.MutableRefObject<any>; //... } ...

Discover an alternative to Events by harnessing the power of Observables to effectively listen for dismiss events in Angular Ionic

Currently, I am utilizing Ionic's inline modal feature that is activated by a boolean value. However, after the modal is closed, the boolean does not automatically reset to zero. The Ionic documentation suggests that developers should monitor the ionM ...

Demystifying the Mechanics of RxJS Subscriptions during an HTTP Request

export class VendorHttpService { result = '0'; constructor(private http: HttpClient, private global: GlobalService) { } getProfileStatus(uid: String): string { this.http.get(this.global.getUrl()+"/vendor/profile-status/"+uid) ...

Challenge with enforcing strong typing in Typescript

Here is the code snippet that I am working with: export type FooParams = { foo1: { x: number }; foo2: { y: string }; }; export type FooKey = keyof FooParams; // or export type FooKey = "foo1" | "foo2"; export interface FooAction&l ...

Keeping the user logged in even after closing the app in an Ionic/Angular project: Best practices to retain user sessions

As a newcomer to angular/ionic, I have been given a project where the user needs to remain logged in even after closing the application unless they explicitly log out. Can anyone provide suggestions on how to modify the code below? login.page.ts import { ...

When webpack and typescript collide: d3.tip goes missing

I am currently working with d3 in combination with Typescript and webpack. Within package.json: "dependencies": { "@types/d3": "^3.5.38", "@types/d3-tip": "^3.5.5", "d3": "^3.5.17", "d3-tip": "^0.7.1", ... }, While my d3 code funct ...

Ensuring TypeORM thoroughly examines all columns with the decorators in NestJS

Is there a method to ensure the uniqueness validator in the TypeORM entity inspects all columns and provides notifications for all detected errors collectively? Consider this schema as an example: import { BaseEntity, Column, Entity, PrimaryGenera ...

I have the capability to successfully input data into Azure table storage, however, I am currently facing difficulties when attempting to retrieve information from the table

Currently, I am using Node.js with TS Express server. My goal is to retrieve data from a table named tracker that also has the partition name as tracker. While I have successfully been able to write to the table without any issues, I encountered an error w ...