Transforming a discriminated union into an object

Looking for a solution to transform a discriminated union similar to this:

type Something = {
   type: 'mode';
   values: 'first' | 'second';
} | {
   type: 'part';
   values: 'upper' | 'lower';
};

into

{
    mode: 'first' | 'second';
    part: 'upper' | 'lower';
}

with the help of a generic type?

I've attempted something along these lines:

type MyUnion = {
   type: string;
   values: string;
};

type DiscUnionToObject<U extends MyUnion> = {
   [V in U['type']]: U['values']
}

however, when calling

DiscUnionToObject<Something>
, I get

{
    mode: 'first' | 'second' | 'upper' | 'lower';
    part: 'first' | 'second' | 'upper' | 'lower';
}

I'm struggling to figure out how to make the generic type recognize that 'upper' | 'lower' should not be included for Something when type is set to mode.

Answer №1

TypeScript doesn't offer all the necessary type operators needed for the desired functionality. It would be ideal to state something like this:

type ConvertUnionToObject<U extends MyUnion> = {
  [V in U['type']]: (U & { type: V })['values']
}

In this scenario, the intersection (U & { type: V }) would extract a single element from the discriminated union. For instance, if U is Something and V is part, it refers to (Something & { type: 'part' }) which should essentially equate to

{type: 'part', values: 'upper'|'lower'}
. However, the compiler fails to recognize this transformation: according to its logic, 'part'&'mode' equals never, yet it doesn't reduce to this conclusion where required.

Hence, such methods are not feasible. Additionally, there's an interest in iterating through unions or intersections to map each element for generating altered unions or intersections, similar to enhanced mapped types. Unfortunately, TypeScript does not support these operations either.


Although TypeScript excels in dynamically creating discriminated unions, analyzing them programmatically poses challenges. In certain situations, you may need to reverse your approach. Initiate with an object structure and derive a corresponding union:

type SomethingObject = {
  mode: 'first' | 'second'
  part: 'upper' | 'lower'
}

type ObjectToDiscUnion<O, V = {
  [K in keyof O]: {type: K, values: O[K]}
}> = V[keyof V]

type Something = ObjectToDiscUnion<SomethingObject>;

The equivalence between Something in this context and your original implementation can be confirmed. Hopefully, this alternative perspective proves beneficial for your endeavors!

Answer №2

Starting from Typescript version 2.8, a new feature allows us to extract individual components of discriminated unions:

Extract<P, { type: 'mode' }>;

This enhancement enables us to achieve the original intended functionality with ease:

type DiscUnionToObject<U extends MyUnion> = {
    [V in U['type']]: Extract<U, { type: V }>['values'];
}

By using this type, calling

DiscUnionToObject<Something>
yields the desired outcome:

{
    mode: 'first' | 'second';
    part: 'upper' | 'lower';
}

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

Looking to enforce a certain type for a function while also setting a default value in TypeScript

Is there a way to enforce a specific type of function with default values for some functions? I experimented with a simple code snippet like the one below: type MyFunc = (str: string) => number; const myAFunc: MyFunc = (str) => Number(str); const m ...

Tips for utilizing the Fluent UI Northstar Color Palette

When working with Fluent UI Northstar, one helpful feature is the color palette. While the documentation provides a list of color names and gradients that can be found here, it can be confusing on how to actually utilize these values (such as 100, 200, e ...

Angular/Typescript ESLint rule for enforcing pure functions

Are there specific ESLint rules to ensure that only pure functions are written in TypeScript? I am looking to strictly write only pure functions in my code. Could someone please provide guidance on how to accomplish this? Appreciate any help in advance ...

Troubleshooting a GET Request Hanging Issue with Next.js 13 Route Handler

I'm currently encountering an issue with the new routing feature in my Next.js 13 project. I have a route handler set up in app/api/ingresos/route.ts with the code snippet below: import { NextResponse } from 'next/server'; import PocketBase ...

Challenges with specifying types in a Typescript login function

Currently facing an issue with the login code, where it is meant to authenticate a username and password, retrieve the corresponding hash from the database, compare them, generate a JWT, and send it back to the user: async login(username, password): Promi ...

Solving the TypeScript error: "Element implicitly has an 'any' type because an expression of type 'string' cannot be used to index type"

I'm having difficulty properly declaring a variable in my code. Here is the code snippet I am working with: ngOnInit(): void { this.activatedRoute.fragment.subscribe(numberOfTab => { if (numberOfTab) { this.tabs[numberOfTab].active = true; } else ...

Is there a way to easily toggle a Material Checkbox in Angular with just one click?

Issue with Checkbox Functionality: In a Material Dialog Component, I have implemented several Material Checkboxes to serve as column filters for a table: <h1 mat-dialog-title>Filter</h1> <div mat-dialog-content> <ng-container *ng ...

Create a one-of-a-kind Angular 6 material table component featuring unique custom columns

My goal is to streamline the process of creating custom material tables by using a specialized table component that allows me to quickly generate unique tables for different data sources with built-in pagination and sorting. All I need to provide are the d ...

What is preventing me from being able to spyOn() specific functions within an injected service?

Currently, I am in the process of testing a component that involves calling multiple services. To simulate fake function calls, I have been injecting services and utilizing spyOn(). However, I encountered an issue where calling a specific function on one ...

What could be causing the type errors I am encountering while trying to resolve this Promise within a generic function?

I am attempting to implement additional types within this WebSocket protocol: type Action = { action: "change-or-create-state"; response: string; } | { action: "get-state"; response: string | null; }; /** * map an action to its response ...

Encountering a Lint No Nested Ternary Error while utilizing the ternary operator

Is there a way to prevent the occurrence of the "no nested ternary" error in TypeScript? disablePortal options={ // eslint-disable-next-line no-nested-ternary units=== "mm&quo ...

What is the best way to send {...rest} properties to a text field in react material?

When using a material textfield inside a wrapper component and passing the remaining props as {...otherprops} in a JavaScript file, everything works fine. However, when attempting to do the same in TypeScript, an error occurs. const TextFieldWrapper = (pro ...

Having difficulty displaying data in the proper format with two-way binding

In the realm of my webpage, I have a plethora of headings, paragraphs, images, and other data at my disposal. From the backend, a dataset is provided to me that includes an array with various properties housing the desired information. The challenge lies i ...

The message "Type expected error TS1110" pops up while trying to utilize the export type feature

While attempting to compile my TS files using grunt-typescript, I encountered the following error: Error TS1110: Expected type. This error appeared on each of the three lines below: export type AttributeWriteType = "Append" | "Replace" | "Static"; expor ...

Validate object containing both static and dynamic keys

I'm attempting to create a Yup validation schema for an object with the following structure: interface myObject { prop0: Date prop1: { nestedProp1: string nestedProp2: number [key: string]: string | number } } This is what I have tr ...

Executing function in component via template

Within the template section <tr *ngFor='let activity of pagedWorkflowActivities' [style.background-color]="setBackgroundColor(activity)"> In the component section setBackgroundColor(activity: WorkflowActivity) { return 'red&apos ...

Angular EventEmitter coupled with Callbacks

In order to create a custom button component for my angular application and implement a method for click functionality, I have the following code snippet: export class MyButtonComponent { @Input() active: boolean = false; @Output() btnClick: EventEmit ...

Using the OR operator in Angular's *ngIf directive within a component's HTML

Need help with an *ngIf statement using OR in Angular, but the second condition is not working as expected. See below for the code: <div *ngIf="router.url != '/one' || router.url != '/two'"> show something </div> Any su ...

Why is there an error being thrown with this Typescript declaration?

I'm currently in the process of familiarizing myself with Typescript. Specifically, I am developing an Angular 2 application using typescript 2 and employing SystemJS 0.15.31 as my module loader. My goal is to establish constants that are accessible ...

What is the process for authenticating and sending a click conversion to Google Ads using a service account and the REST API with TypeScript?

I have attempted to authenticate using this documentation and to send conversions via REST API using this REST endpoint due to the absence of a Typescript/Javascript Client Library. However, I am encountering authentication issues. Once resolved, I aim to ...