Ways to determine a pure logical OR of object types without combining different object keys in the output

For example:

type TA = {a:number,b:number}
type TB = {a:number,c:number,d:number}
const t1:Or<TA,TB> = {a:1,b:1} // expecting
const t2:Or<TA,TB> = {a:1,c:1,d:1} // expecting 
const t3:Or<TA,TB> = {a:1,b:1,c:1} // not expected  

The goal is for t1 to be valid because it exactly fits TA, and t2 to be valid because it exactly fits TB, while t3 should be invalid as it doesn't match either TA or TB.

With the definition:

type Or<T,U>=T|U

In TypeScript, t3 is considered valid. The union type | allows object keys to be merged, resulting in partial objects being accepted.

An alternative approach involves:

type T0 = {a:number,b:number}
type T1 = {a:number,c:number,d:number}
type Test=[T0]|[T1]
const t1:Test=[{a:1,b:1,}]  
const t2:Test=[{a:1,c:1,d:1}]  
const t3:Test=[{a:1,b:1,c:1}] // fails as expected

This method works, but both the object under test and the types have to be placed in single-element arrays.

Is there a way to avoid this limitation?

Answer №1

The Reason

Within TypeScript, the symbol | does not function as an operator; instead, it signifies that a type is a combination of both the left and right types. Understanding this concept is crucial in comprehending why

{a:number,b:number} | {a:number,c:number,d:number}
permits {a:number,b:number,c:number}.

When defining a union, you are informing the compiler that any type assignable to the union must be at least assignable to one of its members. With this in mind, let's analyze the type {a:number,b:number,c:number} from this perspective.

The left side member of the union is {a:number,b:number}, meaning that types capable of being assigned to it must have a minimum of 2 properties with type number: a and b. Referencing the handbook:

the compiler only checks that at least the ones required are present and match the types required

Therefore, since {a:number,b:number,c:number} can be assigned to {a:number,b:number}, no further validation is necessary - the type fulfills at least one condition of the union. This behavior aligns perfectly with the truth table of the logical OR, mirroring the functionality of a union.


Your efforts to resolve this by enclosing the types within tuples stem from the distinction between naked vs. wrapped type parameters. By utilizing tuples, the compiler assesses one-element tuples against each other. Clearly, the third tuple differs from the first two, resulting in the desired outcome.


The Concept

What you truly desire is akin to the logic of XOR: "one of, but not both." Apart from adopting tagged types (as suggested by T.J. Crowder), a utility can be crafted to transform a pair of types into a union encompassing "all properties from A that intersect with B, but not solely part of A":

type XOR<A,B> = ({ [ P in keyof A ] ?: P extends keyof B ? A[P] : never } & B) | ({ [ P in keyof B ] ?: P extends keyof A ? B[P] : never } & A);

Here is an illustration of its application (note that although intellisense hints at excess property leakage, specifying such properties is immediately disallowed due to never):

const t0:XOR<TA,TB> = {a:1} //property 'b' is missing
const t1:XOR<TA,TB> = {a:1,b:1} // OK
const t2:XOR<TA,TB> = {a:1,c:1,d:1} // OK 
const t3:XOR<TA,TB> = {a:1,b:1,c:1} // types of property 'c' are incompatible

Playground


* The claim of | being an operator was included in the initial revision and subsequently removed.

** It is essential to note that not all validations halt when a match is identified. In scenarios where all union members are object literals, the restriction on recognized properties still applies, leading to a TS2322 error if an unknown property is detected during assignment:

const t4:XOR<TA,TB> = {a:1,b:1,c:1, g:3} //Object literal may only specify known properties

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

The result of chaining methods is a generic object return type

My goal is to achieve the following: let result = loader .add<number>(1) .add<string>("hello") .add<boolean>(true) .run(); I am seeking a method to create the hypothetical loader object in a way that automatically deter ...

TypeScript failing to correctly deduce the interface from the property

Dealing with TypeScript, I constantly encounter the same "challenge" where I have a list of objects and each object has different properties based on its type. For instance: const widgets = [ {type: 'chart', chartType: 'line'}, {typ ...

Error encountered while utilizing the Extract function to refine a union

I am currently working on refining the return type of my EthereumViewModel.getCoinWithBalance method by utilizing the Extract utility type to extract a portion of my FlatAssetWithBalance union based on the generic type C defined in EthereumViewModel (which ...

What steps should I take to resolve the issue of "Error: The pipe 'filter' could not be located! Error: The pipe 'filter' could not be found!" in Angular version 10?

I am encountering an issue with a filter that is supposed to search for a specific name and retrieve the data containing that name. However, I am only seeing an error in the browser console, and the page is not displaying as expected. Here is the HTML cod ...

What is the best way to retrieve the instance variable of a component from a handler method in Angular, specifically using the Razorpay API

I am working on integrating Razorpay into my Angular application. I am trying to retrieve the value of the days variable from the handler function, but I am getting an error stating that the property days does not exist. This is a reference to the option ...

Attempting to invoke a TypeScript firebase function

I'm currently working on incorporating Firebase functions in my index.ts file: import * as functions from "firebase-functions"; export const helloWorld = functions.https.onRequest((request, response) => { functions.logger.info(" ...

the directive is not operating correctly

I'm currently working on a directive that restricts user input. With the limit-directive="2", the user should only be able to type in two characters in the input field. Unfortunately, I'm facing an issue with my directive. It is being called, bu ...

Angular 12 Directive causing NG-SELECT disabled feature to malfunction

Looking for a way to disable ng-select using a directive. Does anyone have any suggestions on how to accomplish this? Here is the code I have been working with, along with an example that I was trying to implement. setTimeout(() => { const selectElem ...

Obtaining a dependency directly from the Injector within a directive

I am currently working on developing a versatile directive that can take a class type for validating rules. Depending on the rule specified in the class, the directive will either display or hide an element. This is my progress so far. Check out the PLUN ...

Error: Funktion addChild ist nicht vorhanden in FlexboxLayout

I'm struggling with mounting a visualization for data pulled from a REST API using TypeScript. I keep encountering errors when trying to dynamically create the layout, even though the data is being retrieved correctly from the server. <FlexboxLayo ...

Is there a shorthand for using += with a string type that may be null?

What is the most efficient method to convert this JavaScript code into Typescript? let a, b; /* @type {string | null} */ a += b; One possible solution is let a: string | null, b: string | null; a = a || '' + b || ''; However, this app ...

Extracting the content within Angular component tags

I'm looking for a way to extract the content from within my component call. Is there a method to achieve this? <my-component>get what is here inside in my-component</my-component> <my-select [list]="LMObjects" [multiple]=&qu ...

The fields blush crimson just as my fingers hover over the keyboard, ready

I'm facing an issue where the fields are being validated as soon as I open the page without even typing anything. All the fields appear in red from the beginning, making it difficult for me to input any information. https://i.sstatic.net/l5TGH.png ...

Rollup TypeScript: generating declarations for dist, cjs, and es modules

I am working on packaging a TypeScript library using rollup with declaration (d.ts) files, and I am aiming to generate both cjs and es versions. After brainstorming, I came up with the following rollup configuration: const {nodeResolve} = require('@r ...

Utilize checkboxes for executing send operations and implementing prevention measures in Angular 9

I'm currently working with Angular 9 and I am facing an issue with a form that includes a checkbox. The form is designed in a way that when the user clicks the save button, it should fill in the name field. However, I have noticed that when the checkb ...

What is the best way to simulate mailgun.messages().send() with Jest?

Currently, I am utilizing the mailgun-js Api for sending emails. Instead of a unit test, I've created an integration test. I am now facing the challenge of writing a unit test case for the sendEmail method within the Mailgun class. I am unsure of how ...

Strategies for resolving type issues in NextJs with Typescript

In my project using Next.js with TypeScript, I encountered an issue while trying to utilize the skipLibCheck = false property for enhanced checking. This additional check caused the build process to break, resulting in the following error: Error info - U ...

Building Custom Secure Paths with Keycloak in Next.js - A Step-by-Step Guide

Incorporating Keycloak for authentication in my Next.js application is something I'm currently working on. My goal is to secure certain routes, such as /contact, and allow access only to authenticated users. Although I have successfully set up Keycloa ...

Make sure to incorporate the .gitignored files that are compiled from typescript when running the `npm install -g

Looking for a way to ignore the JavaScript files compiled from TypeScript in my git repository to make merging, rebasing, and partial commits easier. Here's how I have it set up: tsconfig.json { "compilerOptions": { "outDir": "./dist" ...

Exploring the Angular keyvalue pipe and iterating through two separate objects

I've come across some code that I need to refactor, but I'm struggling to fully grasp how to go about it. In my TypeScript file, I have defined the keys that I want to include in an object: keysInclude = { property1: 'Property 1', prope ...