Explain Like I’m Five: TypeScript UnionToIntersection type

Few moths ago while playing around with TypeScript types, I found myself wanting to convert an union to intersection type.

When I realized that I literally have no idea on how to do it,
As any other 10x developer, I googled it – “Union to intersectio…


This content originally appeared on DEV Community and was authored by Anurag Hazra

Few moths ago while playing around with TypeScript types, I found myself wanting to convert an union to intersection type.

When I realized that I literally have no idea on how to do it,
As any other 10x developer, I googled it - "Union to intersection in typescript".

Immediately I found this amazing post by @ddprrt, after going through the post, I had no idea how that UnionToIntersection type worked, It looked like magic to me and didn't even understood the concept of contra-variant/co-variant so I just copy pasted the utility and moved away pondering how it works.

Couple months later when I got better at TypeScript I dived deeper into type variance and got a general idea about it.
This video from @titiancernicova helped me a lot to understand about variance in typescript along with this post, but still I sometimes get confused about variance.

Now, today with this post my aim is to make you understand the UnionToIntersection type without even knowing about variance.
I will explain the type step by step in simple terms so that you can understand it.

Anatomy Of UnionToIntersection

Let's first on a very high level visualize the anatomy of the UnionToIntersection type and see a demo of how it works.

Anatomy Of UnionToIntersection

As we go deeper unpacking the type we will discuss more details about each part.

let's say you have an union like this, which you want to convert to an intersection.

type ContactMethods =
    | { email: string }
    | { phone: number }
    | { address: string }

type User = {
    // Need intersection of ContactMethod here :(
    // something like this { email: string } & { phone: number }
    contact: DoSomeMagic<ContactMethods>
}

Let's drop in the UnionToIntersection type.
As you can see the type is now converted to an intersection.

TS Playground link

type UnionToIntersection<T> = 
  (T extends any ? (x: T) => any : never) extends 
  (x: infer R) => any ? R : never

type User = {
    contact: UnionToIntersection<ContactMethods>
//  ^? { email: string } & { phone: number } & { address: string }
}

Let's Unpack

Naked type & distributive conditional type

So first the type takes an generic parameter T.
type UnionToIntersection<T> =

Then on the second line, we are doing this
(T extends any ? (x: T) => any : never)

Here the T extends any is a naked type, which means that it isn't wrapped in anything.
TypeScript conditional types have a special property that if you pass an union then the conditional type will be applied to each member of that union, given the type is a naked type.

Refer to @ddprrt's article to learn more about this section of the code, I'm skipping the detailed examples here since I don't want to repeat the same.

Learn more about distributive conditional types

Extracting out

Let's extract out the first part of the type to a different utility type for easier understanding,
And go step by step of how the compiler will evaluate the type.

type ToUnionOfFunction<T> = T extends any ? (x: T) => any : never

// Phase 0
type Demo0 = ToUnionOfFunction<
  { a: string } | { b: string }
>
// ↓
// Phase 1 - Union gets distributed to each of it's member
type Demo1 = 
  | ToUnionOfFunction<{ a: string }> 
  | ToUnionOfFunction<{ b: string }>
// ↓
// Phase 2 - `{ a: string }` becomes `(x: { a: string }) => any`
type Demo2 =
    | ((x: { a: string }) => any)
    | ((x: { b: string }) => any)

TS Playground Link

Simple enough right? Basically it converts the passed union to an union of functions.

The key thing here is the distributive conditional type (T extends any,
if we just used (x: T) => any then the type would resolve to:
(x: { a: string } | { b: string }) => any

type ToUnionOfFunction<T> = (x: T) => any;
type Demo = ToUnionOfFunction<{ a: string } | { b: string }>
//   (x: { a: string } | { b: string }) => any

Understanding how the intersection will happen

Now that we have this union of function.

type UnionOfFunctions =
    | ((x: { a: string }) => any)
    | ((x: { b: string }) => any)

let's assign this to a function and see what happens

type UnionOfFunctions =
  | ((x: { a: string }) => any)
  | ((x: { b: string }) => any);

const foo: UnionOfFunctions = () => {};

// 'b' is missing
foo({ a: "hello" });

// 'a' is missing
foo({ b: "world" });

// Works
foo({ a: "hello", b: "world" });

What's happening here is that, to safely call the function foo which has a type of UnionOfFunction we have to pass a type which will satisfy both of the function's requirement. In other words the argument must be { a: string, b: string }

Voila! we get an intersection type in the param.

TS Playground Link

Intersection in function param example

Now all that is left is to get the type of the parameter, which covers the second part of the type.

Second Part

Let's move to the second and final part.
extends (x: infer R) => any ? R : never

In this part we are first checking if the result of ToUnionOfFunction is equal to this (x: infer R) => any, but instead of passing T into the arg we are inferring the argument with the keyword infer

Let's inline all of it:

// Result of ToUnionOfFunction<T>
type UnionOfFunctions =
  | ((x: { a: string }) => any)
  | ((x: { b: string }) => any);

type Demo = UnionOfFunctions extends (x: infer R) => any ? R : never;

Infer keyword is like a magnifying glass of TypeScript which lets us inspect any certain type from the eye of compiler & extract it to a type variable, in this case R

And that's it! 🎉

Let's look at the whole thing at once.

TS Playground link

type ToUnionOfFunction<T> = T extends any ? (x: T) => any : never;

type UnionToIntersection<T> = 
    ToUnionOfFunction<T> extends (x: infer R) => any ? R : never;

type ContactMethods =
  | { email: string }
  | { phone: number }
  | { address: string };

type User = {
  contact: UnionToIntersection<ContactMethods>;
  // ^?
};

Conclusion

I hope you got a basic understanding of how this handy type works.

Now as I said my aim with this post was to explain and breakdown the type without going into the topic of variance, So I oversimplified some things and how the intersection on the param actually happens so,

If you want to learn more about exactly how it works and go digging deeper into this topic here are few resources:

Follow me on twitter for more TS related posts. :) Thanks for reading.


This content originally appeared on DEV Community and was authored by Anurag Hazra


Print Share Comment Cite Upload Translate Updates
APA

Anurag Hazra | Sciencx (2021-12-07T15:36:00+00:00) Explain Like I’m Five: TypeScript UnionToIntersection type. Retrieved from https://www.scien.cx/2021/12/07/explain-like-im-five-typescript-uniontointersection-type/

MLA
" » Explain Like I’m Five: TypeScript UnionToIntersection type." Anurag Hazra | Sciencx - Tuesday December 7, 2021, https://www.scien.cx/2021/12/07/explain-like-im-five-typescript-uniontointersection-type/
HARVARD
Anurag Hazra | Sciencx Tuesday December 7, 2021 » Explain Like I’m Five: TypeScript UnionToIntersection type., viewed ,<https://www.scien.cx/2021/12/07/explain-like-im-five-typescript-uniontointersection-type/>
VANCOUVER
Anurag Hazra | Sciencx - » Explain Like I’m Five: TypeScript UnionToIntersection type. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/12/07/explain-like-im-five-typescript-uniontointersection-type/
CHICAGO
" » Explain Like I’m Five: TypeScript UnionToIntersection type." Anurag Hazra | Sciencx - Accessed . https://www.scien.cx/2021/12/07/explain-like-im-five-typescript-uniontointersection-type/
IEEE
" » Explain Like I’m Five: TypeScript UnionToIntersection type." Anurag Hazra | Sciencx [Online]. Available: https://www.scien.cx/2021/12/07/explain-like-im-five-typescript-uniontointersection-type/. [Accessed: ]
rf:citation
» Explain Like I’m Five: TypeScript UnionToIntersection type | Anurag Hazra | Sciencx | https://www.scien.cx/2021/12/07/explain-like-im-five-typescript-uniontointersection-type/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.