How can I stop union types from being used in TypeScript?

I'm completely new to conditional types, and I recently attempted a straightforward static approach with no success:

type NoUnion<Key> =
  Key extends 'a' ? 'a' :
  Key extends 'b' ? 'b' :
  never;

type B = NoUnion<'a'|'b'>;

Despite my efforts, the type B remains a union. Can someone kindly provide me with some guidance or explanation on this matter?

For those who might be interested, here's a playground.

Answer №1

I'm not exactly sure what the intended use is for this, but it seems that we can set NoUnion to always be never if the input type is a union.

Some have pointed out that conditional types distribute over unions, which is referred to as distributive conditional types

Distributive conditional types occur when the checked type is a naked type parameter. These types are automatically distributed over union types during instantiation. For instance, if T extends U ? X : Y has an argument of A | B | C for T, it is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).

The key point here is 'naked type'; if the type is enclosed in a tuple type, the conditional type will no longer be distributive.

type UnionToIntersection<U> = 
    (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never 

type NoUnion<Key> =
    // If it's a basic type, UnionToIntersection<Key> remains the same, otherwise it creates an intersection of all types in the union and might not extend `Key`
    [Key] extends [UnionToIntersection<Key>] ? Key : never;

type A = NoUnion<'a'|'b'>; // never
type B = NoUnion<'a'>; // a
type OtherUnion = NoUnion<string | number>; // never
type OtherType = NoUnion<number>; // number
type OtherBoolean = NoUnion<boolean>; // never since boolean is essentially true|false

In the last example, there's an issue because the compiler interprets boolean as true|false, resulting in NoUnion<boolean> being never. It may be solved by treating boolean as a special case:

type NoUnion<Key> =
    [Key] extends [boolean] ? boolean :
    [Key] extends [UnionToIntersection<Key>] ? Key : never;

Note: The UnionToIntersection function is sourced from here

Answer №2

Just wanted to share a slightly simpler version I came up with:

( Please note that the code below may not work in TypeScript versions after v3.3, as explained here:

// type NotAUnion<T> = [T] extends [infer U] ? 
//  U extends any ? [T] extends [U] ? T : never : never : never;

Instead of the previous code, you can use this one since defaults are instantiated before distribution, for now at least: )

type NotAUnion<T, U = T> =
  U extends any ? [T] extends [U] ? T : never : never;

I believe this should work (please feel free to test it out; not sure why the original version in my previous answer was incorrect but it's been corrected now). This concept is similar to the UnionToIntersection: ensuring that each part of a type T is assignable to T when it's distributed. Generally, this is only true if T consists of a single constituent part (referred to as "not a union").

Of course, @TitianCernicovaDragomir's solution works perfectly fine too. Just thought I'd share this alternative version. Cheers!

Answer №3

Here is another option that can be utilized:

const NonUnionType<A, B = A> = A extends B ? [B] extends [A] ? A : never : never;

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 determine the data type of the property within the table object

Is it feasible to retrieve the type of object property when that object is nested within a table structure? Take a look at this playground. function table<ROW extends object, K extends Extract<keyof ROW, string>>({ columns, data, }: { col ...

Using TypeScript to implement functions with multiple optional parameters

Imagine having a function like the one shown here: function addressCombo(street1:string, street2:string = "NA", street3?:string) { console.log("street1: " + street1); console.log("street1: " + street2); console.log("street2: " + street3); } I ...

Obtain the appropriate selection in the dropdown based on the model in Angular

I am working on a dropdown menu that contains numbers ranging from 1 to 10. Below is the HTML code for it: <div class="form-group"> <label>{{l("RoomNumber")}}</label> <p-dropdown [disab ...

Can fields from one type be combined with those of another type?

Is it possible to achieve a similar outcome as shown below? type Info = { category: string } type Product = { code: string, ...Info } Resulting in the following structure for Product: type Product = { code: string, category : string } ...

Why do rows in the React-bootstrap grid layout overlap when the screen is resized?

I am working on a simple layout structure with 2 rows: Row 1 consists of 2 columns Row 2 consists of 1 column The goal is to have both rows expand in width and avoid vertical scrolling of the page. However, when resizing the screen, I want the columns of ...

Is there a way to change a typescript enum value into a string format?

I'm working with enums in TypeScript and I need to convert the enum value to a string for passing it to an API endpoint. Can someone please guide me on how to achieve this? Thanks. enum RecordStatus { CancelledClosed = 102830004, Completed = ...

I'm receiving an error code 500 when attempting a patch request on my Express backend - what's causing this issue

Whenever my angular frontend sends a patch request to my express backend, all routes work smoothly except for the patch routes. The specific error message that pops up is: Error: No default engine was specified and no extension was provided. Should I be ...

Trouble with Jest when trying to use route alias in Next.js with Typescript

Currently, I am developing a Next.js App (v13.2.3) using Typescript and have set up a path alias in the tsconfig.json. Does anyone know how I can configure the jest environment to recognize this path alias? // tsconfig.json { "compilerOptions": ...

What causes Typescript to disregard specified argument types in generic method?

While creating this: class AB<Initial, Current = Initial> { array: AB<unknown, unknown>[] = [] push<Next extends any>(ab: AB<Current, Next>) : AB<Initial, Next> { this.array.push(ab) return this as AB ...

Creating fixtures with Playwright is a simple process that can greatly enhance

I have a requirement to set up fixtures where the first fixture is always available (acting as a base class) and the second fixture will vary in different test files (like a derived class). I've implemented the following code which seems to be working ...

Is it necessary to include a package.json file in the /src directory when creating a package?

I am facing a situation where I have a package.json file in both the root directory and the /src folder within my projects. However, during the build process, the /src package.json is the one that gets copied to the /dist folder (and eventually published t ...

The HTMLInputElemet type does not include a Property Rows

Hey there, I'm just starting to dive into Angular and TypeScript. My current challenge is figuring out how to check if a table is empty or not so that I can show or hide a specific div accordingly. I've attempted the following: var rows = docume ...

There was a TypeScript error found at line 313, character 9 in the file @mui/material/styles/experimental_extendTheme.d

Encountering Typescript error while using Material UI component for date range picker Link - https://mui.com/x/react-date-pickers/date-range-picker/ Snippet of the code import * as React from 'react'; import { Dayjs } from 'dayjs'; im ...

Tips for compacting JSON in Typescript

I have encountered a challenge in my coding where we are dealing with quite large data objects. I am exploring the possibility of compressing these data objects to reduce payload size. For instance, if our input json size is 2 MB, can it be compressed to a ...

Mistakes encountered when compiling TypeScript Definition Files

I am looking to convert my JavaScript files (*.js) to TypeScript files (*.ts) in my ASP.net MVC5 application (not an Asp.net Core app). I am using Visual Studio 2015. After downloading the TypeScript Definition Files into the Scripts\typings\ fol ...

What is the approach to forming a Promise in TypeScript by employing a union type?

Thank you in advance for your help. I am new to TypeScript and I have encountered an issue with a piece of code. I am attempting to wrap a union type into a Promise and return it, but I am unsure how to do it correctly. export interface Bar { foo: number ...

When the async keyword is added, the return type in Typescript can vary

This situation is really puzzling to me. I wrote a function to calculate the number of documents in a collection getDocCount(): Promise<number> { return MyModel.countDocuments({}); } Everything seemed fine. However, when I removed async since I ...

Bring in the type "Request" from the express-request-id module

Within our web app that utilizes TypeScript and express, we integrated the npm package express-request-id to enhance our HTTP Requests and responses by adding X-Request-Id headers. We crafted a middleware to assign the request's id to the HTTP respon ...

Specify specific data types for a dynamically generated class method

Imagine having a scenario with the following class: class Example { method1 = this.generateDynamicFunction((param: string) => { return "string" + param; }); method2 = this.generateDynamicFunction((param: number) => { return 1 + param; ...

The TypeScript compiler does not make assumptions about variable types within an if statement

I'm currently tackling the challenge named 'Merge Two Binary Tree' on leetcode with TypeScript. My solution is passing the tests successfully, but the compiler is throwing an error that says: https://i.sstatic.net/KZYmJ.png What's puz ...