Can optional discriminant in TypeScript's discriminated union lead to assigning invalid values?

I have a question about the following scenario:

type P =
  | { foo: boolean; bar?: undefined }
  | { foo: boolean; bar: boolean; baz: boolean };
const p: P = { foo: true, baz: true };

I expected that the only valid values for p would be:

  1. { foo: true }
  2. { foo: true, bar: true, baz: true }

When bar is not optional in both cases, it works as anticipated.

While I could remove bar from the first part of the union, I need to use this type as React props and it must be "discriminated" somehow.

All examples I found online demonstrating this do not include any additional fields. Could including all the fields in both parts of the union with different types/optionality be the only solution?

Answer №1

Typescript object types are flexible, allowing for extensibility rather than strict closed or sealed structures. This flexibility enables objects to have extra properties not explicitly defined in the type, which is essential when extending interfaces or classes:

interface Foo { bar: string; }
interface Baz extends Foo { qux: number; }
const baz: Baz = { bar: "abc", qux: 123 };
const foo: Foo = baz; // valid

Excess property checking serves as a useful linting feature that warns against assigning an object literal with additional properties to a structure that does not expect them:

const fooDirect: Foo = { bar: "abc", qux: 123 }; // error!
// ---------------------------------> ~~~~~~~~
// Object literal may only specify known properties, and 
// 'qux' does not exist in type 'Foo'.

While excess property warnings may cause confusion, they do not impact type safety significantly. They highlight potential mistakes or oversights but can be bypassed by using intermediate variables or scenarios where warnings are not enforced.


In cases like:

type P =
    { foo: boolean, bar?: undefined } |
    { foo: boolean, bar: boolean, baz: boolean };

const p: P = { foo: true, baz: true };

The assignment remains valid even if an excess property warning is triggered. To prevent such assignments, you can introduce optional properties with the `never` type to ensure missing or `undefined` properties, effectively restricting unexpected keys:

type P =
    { foo: boolean, bar?: undefined, baz?: never} |
    { foo: boolean, bar: boolean, baz: boolean };
const p: P = { foo: true, baz: true }; // error

This approach enforces the desired restriction without relying solely on excess property warnings.


Although the behavior observed might be considered an issue in TypeScript (as reported at microsoft/TypeScript#39050), it's primarily related to excess property warnings within discriminated unions. Future changes could align this behavior with expectations, especially concerning optional discriminant properties.

To ensure stricter validation, making discriminant properties required helps enforce constraints, leading to errors when attempting invalid assignments:

type P =
    { foo: boolean, bar: 0 } | { foo: boolean, bar: 1, baz: boolean };
const p: P = { foo: true, bar: 0, baz: true }; // desired error
// -----------------------------> ~~~~~~~~~

const indirect = { foo: true, bar: 0, baz: true } as const;
const pIndirect: P = indirect; // no error

Considering these nuances, utilizing the optional-`never` approach alongside potential future updates in TypeScript ensures robustness in handling object type assignments.


Explore the code on TypeScript Playground

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

Tips for executing the JetBrains WebStorm refactoring tool 'modify ES6 import' for an entire project

Following a project refactor, certain files were relocated to a different npm package, leading to changes in source files to re-export them from their new location (in order to streamline the migration process). Later on, I came across a helpful refactori ...

This error message occurs when a Firestore Trigger in Firebase Cloud Functions encounters a Permissions issue: Error code 7 PERMISSION_DENIED due to

I've been working on integrating a Firebase Cloud Function to update a document in my Firestore database whenever another document is updated. The trigger is functioning correctly, but I'm encountering an error when trying to access the other doc ...

I am confused about the term "can only be default-imported using the 'esModuleInterop' flag", could you explain it to me?

I ran into a puzzling error: lib/app.ts:1:8 - error TS1259: Module '"mongoose-sequence"' can only be default-imported using the 'esModuleInterop' flag and it seems to be related to this line of code: import _ from 'mongoose-sequ ...

Declarations of Typescript React for Props for Specific Element

I am trying to specify types for certain elements like <button> or <input>, but I am unable to differentiate between specific element types. Here is an example: interface Props{ component: React.ComponentProps<"button"> | nev ...

Can an @ArgType be created using a combination of union @Field?

Is there a way for me to define an input / argtype that could either be a ContactCreate or a ContactRead? I'm uncertain if it's feasible. import { ContactCreate } from 'contact/ContactCreate' import { ContactRead } from 'contact/C ...

Best approach for managing Union Types in Angular 16 Templates / Utilizing Type Inference?

Currently, I'm immersed in a project using Angular 16 where my focus lies on applying a reactive declarative method. Oftentimes, I find myself working with Observables that emit varying data types - either successful data or an error object. Each ret ...

Leveraging import and export functionality in TypeScript while utilizing RequireJS as a dependency

I am in the process of transitioning a complex JavaScript application from Backbone/Marionette to TypeScript. While making this shift, I want to explore the benefits of exporting and importing classes using files as modules. Is it necessary to incorporat ...

Show categories that consist solely of images

I created a photo gallery with different categories. My goal is to only show the categories that have photos in them. Within my three categories - "new", "old", and "try" - only new and old actually contain images. The issue I'm facing is that all t ...

Issue with compiling Tailwind styles in Angular 16 development environment

Encountering an issue with Tailwind version 3.4.1 while using dev mode in an Angular application with version 16.2.12. It appears that Tailwind is not detecting changes and fails to recompile the CSS after saving. The styles refresh only once when the .ang ...

The user authentication service indicates that no user is currently logged in

Currently, I am working on implementing a route guard to distinguish between authenticated users and guests. In order to achieve this, I have created an auth-guard service along with an auth service. Although the user data is successfully stored in the loc ...

Ensuring seamless collaboration between Typescript node modules

Is there anyone who has successfully set up a configuration where module 1, using TypeScript, is referencing another module 2 also with TypeScript, and both are utilizing tsd types like node.d.ts? I have been able to compile and use both modules without a ...

Unable to utilize directives that are declared within the app module in child modules when using Angular 6

Currently utilizing Angular 6. I've established a custom directive named StickyPopover by extending NbgPopover and have integrated it into the declaration of app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { ...

Error message: "Error encountered while building Next.js - ReferenceError: 'describe' is not defined"

I am facing difficulties achieving a successful next build without encountering the following error: > Build error occurred { ReferenceError: describe is not defined Although the dev server and tests execute smoothly, it appears that the jest global d ...

I'm trying to resolve the "Uncaught TypeError: Cannot read property '0' of null" error that keeps popping up in my Mobx action within a Firebase and React application. Can anyone offer some guidance on

I've encountered some errors while working on a Mobx React application that occurs when I navigate to the /login page, despite being logged in. Here's a snippet of my code: index.tsx (Code Snippet Here) App.tsx (Code Snippet Here) Login.tsx ...

When using Vue with Vuetify, be aware that object literals can only specify known properties. In this case, the type 'vuetify' does not exist in the ComponentOptions of Vue with DefaultData

Started a fresh Vue project with TypeScript by following this guide: https://v2.vuejs.org/v2/guide/typescript.html If you don't have it installed yet, install Vue CLI using: npm install --global @vue/cli Create a new project and choose the "Manual ...

Display a list of items using *ngFor in a dropdown menu without using the optgroup

Is there a way to group data from *ngFor in the dropdown selection without using optGroup? You can find the JSON file link below: JSON Data Typescript Code getProducts() { if (this.products.length < 1) { this.productService.getProducts ...

Unable to access the 'singleton' property of an undefined value in Adonisjs

An issue arises when the tender attempts to register a Service of singleton type. My approach involves utilizing IocContract AppProvider export default class AppProvider { constructor( protected app: ApplicationContract, protected $container: I ...

Contrasting importing CSS directly in index.html versus using styleUrls in Angular 5

My landing page and dashboard components have different CSS styling, causing one of them to not function properly depending on the CSS. I tried removing these two lines from index.html: <link href="./assets/css/bootstrap.min.css" rel="stylesheet" /&g ...

The proper order for logging in is to first validate the login credentials before attempting

I created a custom validation class to verify if a user is logged in before allowing them access to a specific page. However, after implementing this validation, my program no longer routes me to the intended component. Validation.ts export class UserVal ...

An issue occurred while attempting to pretty-print JSON in Typescript

Currently, I am attempting to utilize https://www.npmjs.com/package/prettyjson in conjunction with Typescript; however, it is unable to locate the module. To begin, I created a package.json file: { "name": "prettyjson-test", "description": "prettyjso ...