Using TypeScript to generate a fresh object from a discriminated union sans the need for casting

Link to TS playground of the following Q

Assume I have this listed type and object declarations:

interface TypeA {
    id: string;
    type: 'A',
    fields: {
        thing1: string;
        thing2: number;
    }
}

const defaultA = {
    thing1: 'hey',
    thing2: 123
}

interface TypeB {
    id: string;
    type: 'B',
    fields: {
        thing3: boolean;
        thing4: number;
    }
}

const defaultB = {
    thing3: true,
    thing4: 456
}

const defaultFields = {
    A: defaultA,
    B: defaultB,
}

type AnyType = TypeA | TypeB

Effort to make a new AnyType by using:

const createNewThing = (type: TypeA['type'] | TypeB['type']): AnyType => {
    return {
        id: 'new id',
        type,
        fields: defaultFields[type],
    }
}

leads to a TS error:

Type '{ id: string; type: "A" | "B"; fields: { thing1: string; thing2: number; } | { thing3: boolean; thing4: number; }; }' is not equivalent to type 'AnyType'.
  Type '{ id: string; type: "A" | "B"; fields: { thing1: string; thing2: number; } | { thing3: boolean; thing4: number; }; }' is not identical to type 'TypeB'.
    Varieties of property 'type' don't match.
      Varies '"A" | "B"' doesn't correspond to vary '"B"'.
        Variation '"A"' doesn't correspond to variation '"B"'.(2322)

To work around this, I can cast the entity being produced as AnyType:

const createNewThing = (type: TypeA['type'] | TypeB['type']): AnyType => {
    return {
        id: 'new id',
        type,
        fields: defaultFields[type],
    } as AnyType;
}

However, if possible, I would prefer to avoid casting. Is there another solution to bypass this besides through casting?

Answer №1

This limitation is specific to typescript: discriminated unions are only applicable to top level fields.

In the scenario you shared, thing1 thing2 thing3 thing4 are all nested under fields. If you move them up a level, your example will function correctly.

To learn more, check out this informative blog post.

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

When jQuery is combined with extending Object.prototype, the result is an error message that states, "c.replace is not

In my open source project, I am utilizing jQuery 1.5 and the following snippet is included in my JavaScript code: /** * Object.isEmpty() * * @returns {Boolean} */ Object.prototype.isEmpty = function () { /** * @deprecated Since Javascript 1.8 ...

how to verify if a variable exists in TypeScript

Is there a recommended method for verifying if a variable has a value in TypeScript 4.2? My variable may contain a boolean value. I'm thinking that using if(v){} won't suffice as the condition could be disregarded if it's set to false. ...

Evaluating file selection and uploading functionality within Spectron

Currently, I am faced with the challenge of writing a test for an electron GUI that includes a choose file dialog. Unfortunately, I do not have access to the inner workings of the GUI. Here is the code snippet I have written: await app.client.chooseFile( ...

Storing Global Types in Angular: A Better Approach

Imagine I possess certain universally applicable enums, interfaces, or extensive classes. For example: export interface LogMessage { code: number; message: string; } Where would be the optimal location to store them? What is considered the best pr ...

Combining 2 dates into a single cell format

I'm struggling to change the date format of two dates displayed in one cell using ag-grid. I have tried creating a new function called dateFormatterr with two parameters, but it doesn't seem to work. Below is a snippet of my code and a screenshot ...

JSON definitions for Google Apps Scripts in TypeScript

Is there a way to obtain Typescript definitions for the raw JSON schema when creating a Google App Script with a cloud function, as outlined in the following link: https://developers.google.com/workspace/add-ons/alternate-runtimes-quickstart I've com ...

When in development mode, opt for the unminified version of the library in Web

My TypeScript project utilizes a forked version of the apexcharts npm package. When building the project with webpack in development mode, I want to use the unminified version of the apex charts library. However, for production, I prefer to stick with the ...

What are the best practices for utilizing generics effectively?

A series of interfaces has been defined: export interface FormData<T extends ControlData = any> { [type: string]: T; } export type FormResult<T extends FormData> = { [type in keyof T]: T[type]; }; export interface ControlData<T = any& ...

What is the best way to determine a comprehensive map of every sub-store, their functions, and what data they contain?

Summary: Can all actions with their payloads be automatically grouped by sub-store in a composite store using a single type entity (e.g., interface)? I have implemented a Redux store with multiple sub-stores structured as follows: There is an action setA ...

Generate Angular component unit tests that involve manipulating the DOM using TypeScript

Currently, I am in the process of developing a UI library in Angular 16. While creating a component like a textarea in my typescript file, I find that manipulating the DOM element at a logical level works smoothly. However, when attempting to write tests, ...

How to specifically activate window resize when the width changes

I am looking to implement custom styling on an element based on the current screenWidth of the window. This can be achieved using the following @HostListener: @HostListener('window:resize', ['$event']) public resize() { // Apply ...

Setting up NextJs in Visual Studio Code with Yarn

When I used yarn create next-app --typescript to set up a TypeScript Next.js application with Yarn, everything seemed to be working fine with the command yarn run dev. However, Visual Studio Code was not recognizing any of the yarn packages that were added ...

Object literals that can be reused

Having a component with a table that contains action buttons, clicking on these buttons triggers the emission of various actions (such as edit, delete, route). getEvent(action: ActionButtons, object: any) { // type: (edit,delete,route), route: info wh ...

Can someone guide me on how to create a feature in JavaScript where users can filter prices by inputting minimum and maximum values?

As a beginner in programming, I am looking to create a filtering feature that allows users to filter results by a specific price range. Users should be able to input a minimum and maximum price to receive a list of items within that range. I am currently ...

Using getters in a template can activate the Angular change detection cycle

When using getters inside templates, it seems that Angular's change detection can get stuck in a loop with the getter being called multiple times. Despite researching similar issues, I have not been able to find a clear solution. Background info: I ...

Filtering an array of objects in Angular5

Attempting to refine the display of products, I noticed a problem where the original array is unintentionally altered, leading to incorrect filtering... categories consists of an array containing objects with categories {title, products[]} I aim for cate ...

What is the best approach to creating multiple dropdowns in ant-design with unique options for each?

It seems like I may be overlooking a simple solution here. Ant-Design dropdowns utilize an array of ItemProp objects to show the options, but this restricts me to having only one list of options. const choices: MenuProps['items'] = [ { label: ...

How to access a component attribute in a JavaScript library method in Angular 8

Within my Angular project, I am utilizing Semantic UI with the code snippet below: componentProperty: boolean = false; ngOnInit() { (<any>$('.ui.dropdown')).dropdown(); (<any>$('.ui.input')).popup({ ...

Using React hooks with Material-UI: Snackbar displaying only on first occasion and not again

I have identified an issue that can be easily reproduced. Steps to replicate: Step 1: Start a react app and include material-ui in the project: prompt> create-react-app mui-test prompt> cd mui-test prompt> yarn add @material-ui/core @material-ui ...

Issue with Angular custom toggle functionality not functioning properly on mobile devices

I've been working on creating a custom toggle feature in Angular. While everything runs smoothly on desktop, I'm encountering issues on mobile devices. On desktop, the toggle works by clicking and dragging to the right, whereas on mobile, it shou ...