Is the type safety of Typescript Discriminated Unions failing on nested objects?

I am working on a project using Typescript 4 where I am trying to create an object with discriminated unions. However, it seems that the type safety is not functioning as expected.

export enum StageType {
    PULL = 'pull',
    FILTER = 'filter',
}

export enum StageMetricProperty {
    IMPORTED = 'imported',
    SKIPPED = 'skipped',
    PIPED = 'piped',
    ERRORS = 'errors',
    PROCESSED = 'processed',
    SENT = 'sent',
    SAVED = 'saved',
}

// Pull stage metrics
export interface PullMetrics {
    [StageMetricProperty.IMPORTED]?: number;
    [StageMetricProperty.PIPED]?: number;
    [StageMetricProperty.ERRORS]?: number;
}
export interface PullReportStage { 
    type: StageType.PULL;
    name: string;
    args: string[];
    status: StageStatus;
    errors: string[];
    metrics: PullMetrics;
}

// Filter Stage Metrics
export interface FilterMetrics {
    [StageMetricProperty.SKIPPED]?: number;
    [StageMetricProperty.PIPED]?: number;
    [StageMetricProperty.ERRORS]?: number;
}
export interface FilterReportStage {
    type: StageType.FILTER;
    name: string;
    args: string[];
    status: StageStatus;
    errors: string[];
    metrics: FilterMetrics;
}

export type ReportStage =
    | PullReportStage
    | FilterReportStage;

Despite defining everything according to the above structure, when I try to create a FilterReportStage object (as shown below), Typescript does not detect any errors with the objects inside the FilterReportStage.metrics property, even though it correctly reports errors for the top-level metric: FilterMetrics object. Why could this be happening?

const metric: FilterMetrics = {
    [StageMetricProperty.IMPORTED]: 123, // <-- Correctly parsed as invalid (as expected).
    [StageMetricProperty.PIPED]: 123, // <-- Valid
};

const sample = {
    type: StageType.FILTER,
    name: 'utils:command',
    args: [],
    errors: [],
    metrics: {
        [StageMetricProperty.IMPORTED]: 123, // <-- Invalid, Typescript does not report aby errors.
        [StageMetricProperty.PIPED]: 123, // <-- Valid
    } as FilterMetrics,
    status: StageStatus.PENDING
} as FilterReportStage;

If anyone has insight into why this behavior is occurring, I would greatly appreciate your guidance.

Answer №1

When utilizing type assertions (for example, as FilterMetrics), you are essentially informing typescript that "I have superior knowledge compared to yours, so refrain from validating my work in this instance. Simply trust that it conforms to the type I specify". Consequently, by instructing typescript not to scrutinize your work, it abstains from doing so.

If validation is desired, opt for conventional types (like : FilterReportStage) instead of type assertions:

const sample: FilterReportStage = {
    type: StageType.FILTER,
    name: 'utils:command',
    args: [],
    errors: [],
    metrics: {
        [StageMetricProperty.IMPORTED]: 123, // <--- an error notification occurs at this point
        [StageMetricProperty.PIPED]: 123,
    },
    status: StageStatus.PENDING
};

Interactive demonstration link

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

Unable to allocate FormArray

Just delved into learning Angular and encountered a snag. I am trying to add and remove textfields for a form, so I attempted the following code in my component.ts file: import {FormBuilder, FormGroup, FormArray } from '@angular/forms'; This is ...

Assign the value of a state by accessing it through a string path key within a complexly

I'm currently attempting to modify a deeply nested value in an object by using a string path of the key to access the object. Here is my setup: const [payload, setPayload] = useState({ name: "test", download: true, downloadConfi ...

Automatically selecting the country phone code based on the country dropdown selection

When the country dropdown is changed, I want the corresponding phone code dropdown to be dynamically loaded. <div class="row"> <div class="col-sm-8 col-md-7 col-lg-6 col-xl-5 pr-0"> <div class="form-group py-2"> <l ...

What could be causing the presence of a "strike" in my typescript code?

While transitioning my code from JavaScript to TypeScript for the first time, I noticed that some code has been struck out. Can someone explain why this is happening and what it signifies? How should I address this issue? Here's a screenshot as an exa ...

What scenarios call for utilizing "dangerouslySetInnerHTML" in my React code?

I am struggling to grasp the concept of when and why to use the term "dangerous," especially since many programmers incorporate it into their codes. I require clarification on the appropriate usage and still have difficulty understanding, as my exposure ha ...

What could be causing the issue of my application not being able to operate on the specified port on Heroku?

After spending two whole days trying to decipher the Heroku error message with no success, I'm still unable to pinpoint what is causing the issue. 2021-07-18T04:27:08.741998+00:00 app[web.1]: {"level":30,"time":1626582428741,&quo ...

Having trouble getting the installed datejs typings to work properly

As I delve into Typescript due to my interest in Angular 2, I have come across the datejs Javascript library. To incorporate it into my Angular 2 project, I went ahead and installed datejs via npm, ensuring that it is correctly listed in my package.json. A ...

Trouble with maps not showing up and the console isn't showing any errors when using @react-google-m

Currently, I am facing an issue while trying to integrate Google Maps by following a blog post that provides instructions on next13 TypeScript. Despite reaching the point where the maps should be displayed, nothing appears on the screen. Surprisingly, ther ...

Issue TS2315: Type 'ElementRef' does not support generics

While attempting to integrate @angular/materials into my application, I encountered a successful compilation with the following error messages: webpack: Compiled successfully. ERROR in node_modules/@angular/material/button-toggle/typings/button-toggle.d.t ...

Mapping strings to enums in Angular is an essential task that allows for seamless communication

Currently, I am facing an issue regarding mapping a list of JSON data to my model. One of the properties in my model is defined as an enum type, but in the JSON data, that property is provided as a string. How can I correctly map this string to an enum val ...

Leveraging WebWorkers in Typescript alongside Webpack and worker-loader without the need for custom loader strings

I've been trying to implement web workers with Typescript and Webpack's worker-loader smoothly. The documentation shows an example of achieving this using a custom module declaration, but it requires the webpack syntax worker-loader!./myWorker. ...

How can I efficiently map an array based on multiple other arrays in JavaScript/TypeScript using ES6(7) without nested loops?

I am dealing with 2 arrays: const history = [ { type: 'change', old: 1, new: 2 }, { type: 'change', old: 3, new: 4 }, ]; const contents = [ { id: 1, info: 'infor1' }, { id: 2, info: 'infor2' }, { id: ...

Breaking apart a combination of unions into a collective group of tuples within TypeScript

Looking for a solution similar to TypeScript extract union type from tuple, this question delves into how to split ['a' | 'b', number] into ['a', number] | ['b', number] rather than focusing on why they are not direc ...

JSON Object Key passed as a parameter in a function

While I have seen a similar question before, I am still unsure how to apply the solution in my specific case. I am working with a function that returns a stringified JSON object, but I need to modify one of the keys using a parameter within the function. ...

Setting up gulp-typescript for Angular 2: A comprehensive guide

Right now I am utilizing the tsc compiler with the watch flag in the command prompt. It functions correctly, loading all definition files and successfully compiling each angular2 file. However, it is quite cumbersome to use via the shell window. My object ...

Sacrificing type safety versus retaining type safety

I'm curious to know what sets apart these two approaches when declaring the status property. I understand that the second version maintains type safety, but how exactly does it achieve this? export type OwnProps = { id: number; name: string; sta ...

The Next.js website displays a favicon in Chrome, but it does not appear in Brave browser

As I work on my debut next.js website, I am configuring the favicon in index.js like this: <Head> <title>Create Next App</title> <link rel="icon" href="/favicon.ico" /> </Head> Initially, all my source ...

Monitoring mouse events on a child element with an attached directive in Angular 7

Is it possible to capture mouse events that occur only within the child element of the directive's host element using @HostListener()? <div listnerDirective class="parent"> <div class="child> <------ Listen for mouse events here ...

TypeScript Add Extract Kind

I am currently working on implementing a function called sumPluck. This function will allow the user to specify a property of type number from an object in an array, and then calculate the sum of all those properties. For example: type A = { prop: number ...

What are the advantages of using interfaces in React?

Exploring Typescript's interface and its application in React has been an interesting journey for me. It seems that interfaces are used to define specific props that can be passed, acting as a form of protection against passing irrelevant props. My qu ...