The Assertion of Field Values in Typescript

I'm currently utilizing https://github.com/stephenh/ts-proto to create TypeScript types from protobuf messages by using type unions for handling `oneOf` fields.

An example of a type is as follows:

export interface Milestone {
  milestoneLabel?: 
     { $case: 'milestoneType', milestoneType: MilestoneType } |
     { $case: 'customMilestone', customMilestone: CustomMilestone };
  targetDate: Date | undefined;
}

Later in the code, I require a function like this:

export function getCustomMilestones(milestones: Milestone[]): Milestone[] {
  return milestones.filter(milestone => milestone.milestoneLabel?.$case === 'customMilestone');
}

The issue arises when the TypeScript language server / compiler does not recognize that the return type of this function includes filtering on the type union, leading to the need to reassert the `$case` field when trying to access it. Is there a method to inform the compiler about the specific type set for the output of the function?

Answer №1

Utilize a custom user type guard:

interface CustomMilestoneWrapper {
  milestoneLabel: { $case: 'customMilestone', customMilestone: CustomMilestone };
  targetDate: Date | undefined;
}

function isCustomMilestoneWrapper(milestone: Milestone): milestone is CustomMilestoneWrapper {
    return milestone.milestoneLabel?.$case === 'customMilestone'
}

function extractCustomMilestones(milestones: Milestone[]): CustomMilestoneWrapper[] {
  return milestones.filter(isCustomMilestoneWrapper);
}

Answer №2

While Lesiak's response is accurate, it's beneficial to provide further insight. Another method to consider is using as for casting to a specific type, assuming the cast is dependable; this serves as a reliable alternative to a standard type guard.

For instance, in the following demonstration (playground):

function filterMilestoneCustom(arg: Milestone[]): Array<Milestone & {milestoneLabel: { $case: 'customMilestone' }}> {
    return arg.filter(milestone => milestone.milestoneLabel?.$case === 'customMilestone') as any;
}

let milestones: Milestone[] = [];

for (let x of filterMilestoneCustom(milestones)) {
    let y: CustomMilestone = x.milestoneLabel.customMilestone;
}

An essential point to note is that there is no need to establish a helper interface. The compiler correctly identifies the type

Milestone & {milestoneLabel: { $case: 'customMilestone' }}
.

The drawback is having to create such a function for each oneof occurrence. A generic solution could address this issue if ts-proto didn't render the oneof property optional.

The concept of the oneof union type in ts-proto originates from my proposal. Subsequently, I developed a protobuf plugin using union types for oneof, employing undefined when nothing is selected. In its basic form, the generated interface looks like:

export interface Milestone {
    milestoneLabel:
        { $case: 'milestoneType', milestoneType: MilestoneType } |
        { $case: 'customMilestone', customMilestone: CustomMilestone } |
        { $case: undefined };
    targetDate: Date | undefined;
}

This enables the creation of a type guard and filtering function as demonstrated below:

type MilestoneCase<C extends Milestone["milestoneLabel"]["$case"]> = Milestone & {milestoneLabel: {$case: C}};

function isMilestoneCase<C extends Milestone["milestoneLabel"]["$case"]>($case: C, milestone: Milestone): milestone is MilestoneCase<C> {
    return milestone.milestoneLabel.$case === $case;
}

function filterMilestoneCase<C extends Milestone["milestoneLabel"]["$case"]>(milestones: Milestone[], $case: C): MilestoneCase<C>[] {
    const is = (ms: Milestone): ms is MilestoneCase<C> => isMilestoneCase($case, ms);
    return milestones.filter(is);
}

This approach can be applied in scenarios like the one depicted in the playground link provided above.

If additional cases are added to the oneof, there is no requirement to develop a new function separately.

Visit protobuf-ts for insights on the associated plugin functionality. Notably, compared to ts-proto, this plugin is built entirely from scratch without dependencies on protobuf.js or Long.js. Moreover, the code size for web applications is significantly reduced, enabling innovative capabilities with reflection and custom options.

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

Angular MistakeORAngular Error

Every time I refresh the page, I encounter an error in my code while attempting to display a newly edited and saved text. I've initialized the variable, made the access variable public, but it still doesn't work. Can someone point out what I migh ...

Save this code snippet to your clipboard using vanilla JavaScript (no jQuery needed)

I am working on an Angular 9 application where I want to implement the functionality of copying the URL to clipboard when clicked. Currently, I have the following code: The issue I am facing is that it only copies the URL on the second attempt and then st ...

Using Typescript and React to retrieve the type of a variable based on its defined type

Just getting started with Typescript and could use some guidance. Currently, I'm using React to develop a table component with the help of this library: Let's say there's a service that retrieves data: const { data, error, loading, refetc ...

Removing the mousedown event from elements within a child component: tips and tricks

Two components are involved: DashboardView and DashboardOrderCard. My goal is to prevent the mousedown event from being emitted when either the date picker is clicked or an option is selected from the DashboardOrderCard. How can I accomplish this? Below is ...

Exploring a different approach to utilizing Ant Design Table Columns and ColumnGroups

As per the demo on how Ant Design groups columns, tables from Ant Design are typically set up using the following structure, assuming that you have correctly predefined your columns and data: <Table columns={columns} dataSource={data} // .. ...

Issue with TypeORM Many-to-Many relation returning incorrect data when using the "where" clause

I'm facing an issue with my database tables - User and Race. The relationship between them is set as Many to Many , where a Race can have multiple Users associated with it. My goal is to retrieve all the Races that a particular user is a member of. Ho ...

Error: Angular 4.1.3 routing is unable to locate child module within parent module

Thanks in advance for any help! Greetings, I'm encountering the following error as described in the title. Despite my attempts at troubleshooting and research, I am unable to resolve it. Below are some important files - can anyone with experience in ...

Combining Promise.all with multiple distinct data types for returned values

Can Promise.all() accept an iterable with different resolved types? For example, can promise.all([promiseA, promiseB, promiseC]) work if promiseA and promiseB return void but promiseC returns boolean? I attempted this and it seems not possible. I am unsu ...

Is it possible to use a type predicate to return `void` from a function?

When creating data validation APIs, I have a common approach where I include two functions - one that returns a boolean value and another that throws an error. The throwing function typically has a void return type. interface MyType { numberField: num ...

Utilizing Variables in TypeScript to Enhance String Literal Types

Here are my codes: export const LOAD_USERS = 'LOAD_USERS'; export const CREATE_USER = 'CREATE_USER'; export interface ACTION { type: string, payload: any } I am trying to limit the possible values for ACTION.type to either 'L ...

Tips for invoking an Android function from an AngularJS directive?

I am facing an issue with my HTML that uses an AngularJS directive. This HTML file is being used in an Android WebView, and I want to be able to call an Android method from this directive (Learn how to call Android method from JS here). Below is the code ...

The routerlink feature consistently directs back to the default page

I am facing an issue where my routerlink does not redirect me to the correct path in app.routes.ts when clicked. Even though the routerlinks are set as 'user/teams' and 'user/dashboard' respectively. I can access the pages by directly ...

To achieve this, my goal is to have the reels start playing on a separate page when a user clicks on the designated image. I am currently working on a project that involves this

When a user clicks on the designated image, I want the reels to start playing on a separate page. In my main project, I have a reels project within it, with the reels project built in ReactJS and the main project in React TypeScript. For example, if a user ...

Having trouble with the react event handler for the renderedValue component in Material UI?

I am facing an issue while trying to utilize the onDelete event handler within the chip component using Material UI in the code snippet below. Upon clicking on the chip, it triggers the Select behavior which opens a dropdown menu. Is there a way to modif ...

What is the best way to refresh the data in my list when a subitem is updated through a GET request using RTK Query?

Having a good understanding of the RTK query concept, I am facing a specific use case where I require some guidance. In my application, there is a list component and a details component that allows users to navigate and view more information about a parti ...

Expanding Angular FormGroup Models with TypeScript

I've developed a foundational model that serves as a base for several other form groups. export class BaseResource { isActive: FormControl; number: FormControl; name: FormControl; type: FormControl; constructor( { ...

calling an Angular template function

Can the convert function be called in an Angular template like covert{{item.size}}? If so, what is the correct syntax to use? Thank you. <mat-cell *matCellDef="let item" fxHide fxShow.gt-xs fxShow.gt-md [matTooltip]="item.size"> ...

Tips for creating an interface in TypeScript with multiple properties of a specific type

I am looking to create an interface that can have properties of the same type, regardless of the content. For example: type Reducer<S, P> = (state: S, payload: P) => S interface Reducers { [name: string]: Reducer } This is how I am trying to ...

leveraging office-ui-fabric in a dotnet core and react app

I am working on a dotnet core and react application and would like to incorporate the Office-ui-fabric library. package.json { "name": "app_fabric_test", "private": true, "version": "0.0.0", "devDependencies": { "@types/history": "4.6.0", ...

Develop an Angular 6 application that utilizes an observable to monitor changes in a variable

I am working with Angular 6 and I need to monitor a variable for any changes and then stop or unsubscribe when the variable has a value. My initial thought was to use an Observable: myValue; // The variable that needs to be monitored myObservable = Obse ...