What is the best way to merge unions in TypeScript?

I'm facing a challenge where I need to combine two types a and b into a new type c:

type a = { a: "hi", b: number }
type b = { a: "bye", b: number }

The desired outcome is:

type c = { a: "hi" | "bye", b: number };

I attempted the following approach:

import { UnionToIntersection } from 'utility-types'

type c = UnionToIntersection<a | b> // however, it results in "never"

This method unfortunately leads to c being inferred as never.


Update*

Here's another scenario that requires transformation:

type o = {
  type: "bar";
  foo: string;
  bar: number;
} | {
  type: "baz";
  foo: string;
  baz: boolean;
}

The goal is to convert this type into:

type: "bar" | "baz"  ;
foo: string;
bar?: number;
baz?: boolean;

Answer №1

What you're requesting is quite similar to an identity operation; if you have a union type like A | B and access its properties, each property automatically becomes unions. If this is all you need, then you can just use the union as-is, or alternatively, create a mapped type to consolidate the union into a single object type.

The slight complication here is that you want properties appearing in only some of the union members to be optional in the combined type, whereas a union tends to omit such properties. So, the initial step is to convert each union member into a new type with optional properties of type never for any undefined property in that member. Essentially, we aim to transform {a: 0, b: 1} | {b: 2, c: 3} into something akin to

{a: 0, b: 1, c?: never} | {a?: never, b: 2, c: 3}
. Subsequently merging these will result in {a?: 0, b: 1 | 2, c?: 3} as desired.

The integration appears as follows:

type _Combine<T, K extends PropertyKey = T extends unknown ? keyof T : never> =
    T extends unknown ? T & Partial<Record<Exclude<K, keyof T>, never>> : never;

type Combine<T> = { [K in keyof _Combine<T>]: _Combine<T>[K] }

In this scenario, _Combine<T> serves as a utility type utilizing distributive conditional types to divide T into union members and execute operations on them. The primary purpose of the generic parameter default value for K is to compile all keys from the members of T (

T extends unknown ? keyof T : never
). Simultaneously, we intersect every member of T with an object incorporating optional keys of type never for each key in
K</code not facilitated by that specific member of <code>T
.

Conversely, Combine<T> represents an identity mapped type over _Combine<T>, streamlining complex instances like

({a: 0, b: 1) & Partial<Record<"c", never>>) | ({b: 2, c: 3} & Partial<Record<"a", never>>)
toward the anticipated {a?: 0, b: 1 | 2, c?: 3}.


Let's test it out with your sample:

type O = {
    type: "bar";
    foo: string;
    bar: number;
} | {
    type: "baz";
    foo: string;
    baz: boolean;
}

type Z = Combine<O>;
/* type Z = {
    type: "bar" | "baz";
    foo: string;
    bar?: number | undefined;
    baz?: boolean | undefined;
} */

Everything seems satisfactory. The essential type and foo properties are mandatory since they appear in all members of O, while the additional bar and baz properties are elective, given their absence from at least one O member.

Playground link for code

Answer №2

Simply utilize

const result = exampleFunction(parameter);

Since b is a frequently used property, it will merge seamlessly without any alterations, while a will be merged as

"hello" | "goodbye"
.

Although some IDEs may display this as

{ a: "hello", b: boolean } | { a: "goodbye", b: boolean }
rather than
{ a: "hello" | "goodbye", b: boolean }
, the functionality remains unchanged.

View a live example here: JS Playground

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

NextJS Typescript Layout is throwing errors due to the absence of required props

After following the instructions on https://nextjs.org/docs/basic-features/layouts#with-typescript and making changes to my Home page as well as _app.tsx, I encountered an issue with the layout file Layout.tsx. The provided guide did not include an exampl ...

Retrieve a particular element from an array within a JSON object using Ionic

I am currently facing a challenge in extracting a specific array element from a JSON response that I have retrieved. While I can successfully fetch the entire feed, I am struggling to narrow it down to just one particular element. Here is what my service ...

Function in nodejs throwing an error: Return type missing

I am facing an issue with this code snippet while trying to compile the application. public async test(options?: { engine?: Config }): Promise<any> { const hostel = new Service({ list: this.servicesList, createService ...

What are some characteristics I can examine in TypeScript?

My understanding of how property checking works in TypeScript was put to the test recently. I noticed that in a specific example, checking for .bold worked fine, but when trying to check for .type, I ran into some confusion. type CustomText = { bold: ...

Using TypeScript with React: Updating input value by accessing event.target.nodeValue

As a newcomer to TypeScript, I am in search of an elegant solution for the following dilemma. I have a state variable named emailAddress, which is assigned a value from an input field. Additionally, I need the input field to display and update its value ba ...

Fixing Typescript assignment error: "Error parsing module"

Trying to assign an object to the variable initialState, where the type of selectedActivity is Activity | undefined. After using the Nullish Coalescing operator (??), the type of emptyActivity becomes Activity. However, upon execution of this line, an err ...

JavaScript - Employing the .every function with an array containing objects

Is it possible to use the array.every method on multidimensional arrays? The structure of my array is as follows: tabs=[ {label: string, icon: icon, routerLink: link}, {label: string, icon: icon, routerLink: link}, {label: string, icon: icon, routerLink: ...

Using the useContext hook across multiple files without needing to export it

I am working on a React app that has multiple states being managed function App(){ const [activeChoice, setActiveChoice] = useState("flights"); const [overlay, setOverlay] = useState(false); const [airports, setAirports] = useState([]); const [loading, ...

Angular 4 incorporates ES2017 features such as string.prototype.padStart to enhance functionality

I am currently working with Angular 4 and developing a string pipe to add zeros for padding. However, both Angular and VS Code are displaying errors stating that the prototype "padStart" does not exist. What steps can I take to enable this support in m ...

What is the correct way to trigger an event specified as a string parameter in the emit() function?

My current goal is to pass the emit name as a string (for example, 'showComponent') from child to parent. I then want to trigger another emit in the emitAction(callbackName: string) function, and finally execute the showComponent() function. I&a ...

Comparing React hooks dependency array with TypeScript discriminated unions

In my React app using TypeScript, I encountered a situation where a component's props type is a discriminated union to prevent invalid combinations of props at compile time. However, I faced a dilemma when trying to pass some of those props into a Rea ...

The unique characteristics of annotations' shapes and placements in the realm of highcharts

After some experimentation, I have successfully managed to set the size of an individual annotation using this code snippet: labels: [ { point: { x: chart.xAxis[0].max - 0.1, y: 50, ...

The current value of React.createRef() is perpetually empty

Ever since I started working on this code, I've been encountering a problem that seems to have no solution. Here's what's going on: import React, { Component } from 'react'; export class InfoPaneArrow extends Component<InfoPane ...

Error encountered while retrieving data from Firebase and storing it in an array within an IONIC application

I am currently working on a function that retrieves data from Firebase's real-time database and stores it in an array for mapping in React. However, I am encountering a TypeScript error that I'm having trouble resolving. The error message reads ...

Angular Reactive Forms - Adding Values Dynamically

I have encountered an issue while working with a reactive form. I am able to append text or files from the form in order to make an http post request successfully. However, I am unsure about how to properly append values like dates, booleans, or arrays. a ...

"Using Angular SSR necessitates the utilization of JSON files without file extensions

Embarking on a new project involving Angular SSR, I am faced with the challenge of importing a JavaScript library that includes a JSON file using the following syntax: var json = require('myfile'); The myfile.json file does indeed exist. Howev ...

TS7053: The element is implicitly assigned an 'any' type as the expression of type 'string' cannot be used to index the type '{ username: string; email: string; '

Having trouble incorporating TypeScript into a custom React Form Component, and I keep encountering an error that I can't seem to resolve. Error message TS7053: Element implicitly has an 'any' type because expression of type 'string&apo ...

Displaying user input data in a separate component post form submission within Angular

I recently developed an employee center app with both form and details views. After submitting the form, the data entered should be displayed in the details view. To facilitate this data transfer, I created an EmployeeService to connect the form and detail ...

Using Inheritance to Create Custom Event/Callback Handlers in TypeScript

Currently facing an issue with Typescript that I'm stuck on. I have created a named callback Handler class that receives the allowed list of "events"/"handlernames" as a generic: class NamedHandler<H extends { [key: string]: HandlerFunction }> ...

The `setState` function is failing to change the current value

I'm having an issue with setting State in the dropdown component of semantic-ui-react while using TypeScript in my code. The selected category value is always returning an empty string "". Any suggestions on how to resolve this problem? impo ...