Restrict the discriminator value in a discriminated union from any other string literal union type in TypeScript

My discriminated union is quite basic, but I want to restrict the discriminator to only allow specific values that come from another string literal union type. This would simplify the process of adding new "cases" to the discriminated union.

Here is a string literal union describing the permitted "values" for the type:

type AllowedType = "typeForNumber" | "typeForBoolean"

The data type I use describes the data using that string literal union:

type Data = {
  type: AllowedType,
  value: number | boolean
}

In my example, there are two options where the value field can have a more specific type based on the type field. Although these options are not utilized in practice, they serve as demonstrations:

// Option 1 - "value" is a "number"
type DataOption1 = {
  type: "typeForNumber",
  value: number
}
// Option 2 - "value" is a "boolean"
type DataOption2 = {
  type: "typeForBoolean",
  value: boolean
}

What I actually aim to achieve is a discriminated union for Data, allowing me to specify more precise types for its value field:

type Data =
  | {
      type: "typeForNumber"
      value: number
    }
  | {
      type: "typeForBoolean"
      value: boolean
    }

When utilizing this type, everything functions correctly:

const myData: Data = {
  type: "typeForNumber",
  // this will trigger an error as it should be a `number`
  value: "some string"
}

My question is: How can I ensure that the type field in my Data type is restricted to the options in the AllowedType?

As additional options for AllowedType may arise in the future, I wish to confine the possible union types accordingly.

One could add another union to the Data type without encountering any errors:

type Data =
  | {
      type: "typeForNumber"
      value: number
    }
  | {
      type: "typeForBoolean"
      value: boolean
    }
  | {
      type: "someOtherType"
      value: string
    }

However, this new union (with type: "someOtherType") should not be allowed.

Is it feasible to restrict the discriminator (type) within this discriminated union (Data) to follow the guidelines set by another string literal union type (AllowedType)?

Although I attempted to utilize a wrapper within the intersection, the unions appear to disregard (or overwrite) the type definition:

type AllowedType = "typeForNumber" | "typeForBoolean"

type DataWrapper = {
  type: AllowedType
  value: number | boolean
}

type Data = DataWrapper &
  (
    | {
        type: "typeForNumber"
        value: number
      }
    | {
        type: "typeForBoolean"
        value: boolean
      }
  )

Answer №1

If my understanding is correct, the issue arises when Data and AllowedType fall out of sync. To address this, you could redefine AllowedType in terms of Data, as shown below:

type Data =
  | {
      type: "typeForNumber"
      value: number
    }
  | {
      type: "typeForBoolean"
      value: boolean
    };

type AllowedType = Data["type"];

Link to Playground

This approach ensures that making additions to Data will automatically reflect in AllowedType.


In a previous comment, it was mentioned that the goal is to prevent typos in the type field of the Data union:

For scenarios where Data represents dynamically sourced information, ensuring accuracy in type values (such as typeForNumber) is crucial. Therefore, finding a solution to restrict types to specific values is essential.

The following code snippet addresses this concern:

type CheckData<DataType extends {type: AllowedType}> =
    Exclude<DataType["type"], AllowedType> extends never
        ? DataType
        : never;

Using this method:

type Data = CheckData<
    | {
        type: "typeForNumber"
        value: number
    }
    | {
        type: "typeForBoolean"
        value: boolean
    }>;

However, the following case will not pass validation:

type Data2 = CheckData<
    | {
        type: "typeForNumber"
        value: number
    }
    | {
        type: "typeForBoolean"
        value: boolean
    }
    | {
        type: "typeForSomethingElse"
        value: boolean
    }>;

Playground Link

Please note that while this solution enhances error detection, it may still allow multiple types with identical type values.

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

How can we import the entire Jasmine library using CucumberJS?

I am currently trying to implement unit testing using Jasmine and CucumberJS in my Angular v9 application. I have followed the tutorial provided by cucumber.io to set up cucumber as the default runner. However, I am facing difficulties in using Jasmine met ...

The functionality of Angular 6 Material Nested Tree is disrupted when attempting to use dynamic data

In Angular 6, I am utilizing mat-tree along with mat-nested-tree-node. My objective is to dynamically load the data when the user toggles the expand icon. Attempting to apply the dynamic data concept from the Flat Tree example provided in Material Example ...

My React JS page suddenly turned blank right after I implemented a setState() function within my functional component

I was working on my code and everything seemed fine until I tried to incorporate the setState function with setcategory and setvalue. However, after making this change, my react page suddenly went blank. Can anyone help me identify what went wrong and pr ...

The Angular 2 and TypeScript error stating that the argument of type response is not assignable is causing issues

In my Angular application, I have a service that includes a value defined as: private client_comments = new BehaviorSubject([]); When attempting to update this value with the response from an HTTP request, I encountered the following error message: A ...

Utilize an array as the response model in Amazon API Gateway using the AWS CDK

I am currently in the process of developing a TypeScript AWS CDK to set up an API Gateway along with its own Swagger documentation. One of the requirements is to create a simple endpoint that returns a list of "Supplier", but I am facing challenges in spec ...

The Angular Cli seems to be having trouble loading a State property and its reducer within the ngrx store all of

Following some minor changes to my application, I encountered an issue with my ngrx store not loading properly. While most of the store properties are displaying as expected and even fetching API results through their reducers, I am noticing that a crucial ...

gulp-angular2 task is malfunctioning

Currently, I am in the process of working on a gulpfile and have written the following task: var tsProject = ts.createProject('app/Resources/public/angular/tsconfig.json'); gulp.task('angular-2', function () { var tsResul ...

How do I properly type when extending Button and encountering an error about a missing component property?

Currently in the process of transitioning from MUI v3 to v4. My challenge lies with some Button components that are wrapped and have additional styling and properties compared to the standard Material UI Button component. Ever since upgrading to v4, I&apos ...

Enhancing NG Style in Angular 6 using a custom function

Today, my curiosity lies in the NG Style with Angular 6. Specifically, I am seeking guidance on how to dynamically update [ngStyle] when utilizing a function to determine the value. To better illustrate my query, let me present a simplified scenario: I ha ...

Is TypeScript declaration merging not functioning properly?

Trying to enhance an existing interface with a new member is causing Typescript errors for me. // foo.js export interface IOption { xOffset: number } import {IOption} from 'foo'; // Attempting to extend IOption with `yOffset`, but encounter ...

Navigating to an external link directing to an Angular 5 application will automatically land on

I am struggling to comprehend why a link from an external source to my Angular app keeps redirecting to the default route page when accessed from a browser. The scenario involves a user entering an email address, triggering an API that sends an email cont ...

The data type 'string | boolean | null' cannot be assigned to type 'boolean'. Specifically, the type 'null' cannot be assigned to type 'boolean'

Can you spot the issue in this code snippet? isAuthenticated(): boolean { var token = localStorage.getItem(ACCESS_TOKEN_KEY); return token && !this.jwtHelper.isTokenExpired(token); } Error: The variable is returning a type of 'string | bo ...

Angular: merging multiple Subscriptions into one

My goal is to fulfill multiple requests and consolidate the outcomes. I maintain a list of outfits which may include IDs of clothing items. Upon loading the page, I aim to retrieve the clothes from a server using these IDs, resulting in an observable for e ...

Ways to evaluate a String that is thrown using Jest

I encountered a scenario where a function throws a string. Although Jest provides the toThrow matcher for testing functions that throw errors, it only works when an Error object is thrown. Is there a way to test if a string is thrown using Jest? The giv ...

The data type does not match the expected type 'GetVerificationKey' in the context of express-jwt when using auth0

I am in the process of implementing auth0 as described here, using a combination of express-jwt and jwks-rsa. However, I encountered an error like the one below and it's causing issues with finishing tsc properly. Error:(102, 5) TS2322: Type 'S ...

Using Regular Expressions as an Alternative to Conditionals

My knowledge of RegEx is limited, but I'm trying to make the following expression work with Javascript/Typescript: /^({)?(?(1)|(\()?)[0-9A-F]{8}(-)?([0-9A-F]{4}(?(3)-)){3}[0-9A-F]{12}(?(1)}|(?(2)\)))$/i This RegEx is used to check if a str ...

How can TypeScript limit the number of properties allowed to be passed as a function parameter?

Here is what I currently have: export interface RegionNode { nodeSelected: boolean; nodeEditable: boolean; zone: Partial<Zone>; parent: RegionNode | null; children: RegionNode[]; } Now, I am looking for a sleek solution to create ...

Playwright failing to execute GraphQL tests due to TypeScript configuration problems

I'm facing an issue with my repo where I am running tests using Playwright against a graphQL URL. Despite configuring the tests, there is an error indicating that the environment variable defining the environment cannot be found. The repository in qu ...

Tips for utilizing the polymorphic feature in TypeScript?

One of the challenges I am facing involves adding data to local storage using a function: add(type: "point" | "object", body: FavouritesBodyPoint | FavouritesBodyObject) { // TODO } export interface FavouritesBodyPoint {} export in ...

Extract TypeScript classes and interfaces from a consolidated file

I am seeking a way to consolidate the export of my classes, interfaces, and enums from multiple files into a single file. In JavaScript, I achieved this using the following method: module.exports = { Something = require("./src/something").default, ...