Is it possible to check if a given type is a union?
type IsUnion<T> = ???
Why I need this: in my code, I have the only case when some received type can be a union. I handle it with a distributive conditional type. However, it can be not obvious for one who looks at this code why a DCT is used in the first place. So I want it to be explicit like: IsUnion<T> extends true ? T extends Foo ...
I've made a few attempts with UnionToIntersection
, with no results. I've also come up with this one:
type IsUnion<T, U extends T = T> = T extends any ? (U extends T ? false : true) : never
It gives false
for non unions, but for some reason it gives boolean
for unions... And I have no idea why. I also tried to infer
U from T, with no success.
P.S. My use case may seem to someone as not perfect/correct/good, but anyway the question in the title has arised and I wonder if it's possible (I feel that it is, but am having hard time to figure it out myself).
So it seems I've come up with an answer myself!
Here is the type (thanks Titian Cernicova-Dragomir for simplifying it!):
type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true type Foo = IsUnion<'abc' | 'def'> // true type Bar = IsUnion<'abc'> // false
And again UnionToIntersection of jcalz came in handy!
The principle is based on the fact that a union A | B
does not extend an intersection A & B
.
Playground
UPD. I was silly enough to not develop my type from the question into this one, which also works fine:
type IsUnion<T, U extends T = T> = (T extends any ? (U extends T ? false : true) : never) extends false ? false : true
It distributes union T
to constituents, also T
and then checks if U
which is a union extends the constituent T
. If yes, then it's not a union (but I still don't know why it doesn't work without adding extends false ? false : true
, i.e. why the preceding part returns boolean
for unions).
NOTE: This answer was for a case where someone explicitly did not want to use
UnionToIntersection
. That version is simple and easy to understand, so if you have no qualms aboutU2I
, go with that.
I just looked at this again and with the help of @Gerrit0 came up with this:
// Note: Don't pass U explicitly or this will break. If you want, add a helper // type to avoid that. type IsUnion<T, U extends T = T> = T extends unknown ? [U] extends [T] ? false : true : false; type Test = IsUnion<1 | 2> // true type Test2 = IsUnion<1> // false type Test3 = IsUnion<never> // false
Seemed like it could be further simplified and I'm pretty happy with this. The trick here is distributing T
but not U
so that you can compare them. So for type X = 1 | 2
, you end up checking if [1 | 2] extends [1]
which is false, so this type is true
overall. If T = never
we also resolve to false
(thanks Gerrit).
If the type is not a union, then T
and U
are identical, so this type resolves to false
.
There are some cases in which this doesn't work. Any union with a member that's assignable to another will resolve to boolean
because of the distribution of T
. Probably the simplest example of this is when {}
is in the union because almost everything (even primitives) are assignable to it. You'll also see it with unions including two object types where one is a subtype of the other, i.e. { x: 1 } | { x: 1, y: 2 }
.
extends
clause (like in Nurbol's answer)(...) extends false ? false : true;
never
as the false case:T extends unknown ? [U] extends [T] ? never : true : never;
extends
at the call site:true extends IsUnion<T> ? Foo : Bar;
type IfUnion<T, Yes, No> = true extends IsUnion<T> ? Yes : No;
There are a lot of other variations that you can do with this type depending on your needs. One idea is to use unknown
for the positive case. Then you can do T & IsUnion<T>
. Or you could just use T
for that and call it AssertUnion
so that the whole type becomes never
if it's not a union. The sky's the limit.
Thanks to @Gerrit0 and @AnyhowStep on gitter for finding my bug & giving feedback on workarounds.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With