Repository navigation
Type manipulations: union to tuple #13298
Description
Activity
functionofwould not hurt either: #12265Reacted by Piotr Witek, Jeroen Meijer (Jay) and Brandon Bennett- addedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on May 24, 2017 You can already do
tuple -> unionconversion:[3, 1, 2][number] // => 1 | 2 | 3 type U<T extends any[], U = never> = T[number] | U U<[3, 1, 2]> // => 1 | 2 | 3 U<[1], 2 | 3> // => 1 | 2 | 3
How about a concat operator for
union -> tupleconversion?type U = 1 | 2 | 3 type T = [0] + U // => [0, 1, 2, 3] type S = U + [0] // => [1, 2, 3, 0] type R = [1] + [2] // => [1, 2] type Q = R + R // => [1, 2, 1, 2] type P = U + U // Error: cannot use concat operator without >=1 tuple type O = [] + U + U // => [1, 2, 3, 1, 2, 3] type N = [0] + any[] // => any[] type M = [0] + string[] // Error: type '0' is not compatible with 'string' type L = 'a' + 16 + 'z' // => 'a16z'
Are there good use cases for preserving union order? (while still treating unions as sets for comparison purposes)
Reacted by ZpdDG4gta, gtkatakura, Ryan Smith, kiara, Abraham White, Kael, Matt Clarkson, Daniel Dietrich, Karol Majewski, hrsh7th and 51 moreReacted by Mina Luke, Danyil Moroz, TenviLi, Morteza Tourani, Ody, Paweł Czarnecki, Erik, Robert Gruner, The web walker, Sarfaraz Nawaz and 3 moreReacted by Enterprise Software Architecture and The web walkerI had used a conditional type for tuple to union:
type ElementOf<T> = T extends (infer E)[] ? E : T;
Works for both arrays and tuples.
Reacted by Ryan Smith, Miloš Lajtman, OKUNOKENTARO, Warren Seymour, pierre, SlurpTheo, Jendrik, Max Sysoev, Arthur Zahorski, Yang Yang and 9 moreReacted by Michał Miszczyszyn, Max, Jacob Weisenburger, Akara, chenqiong, YCM Jason and ericbiewenerReacted by Alec Larson, Daniil Samoylov, Logan Call, Warren Seymour and ErikReacted by Mina Luke, Yue Liu, ૮༼⚆︿⚆༽つ, Robert Gruner and Erikor just [1,2,3][number] will give you 1 | 2 | 3
Reacted by Titian Cernicova-Dragomir, csha, Ashlynne Mitchell, Michał Miszczyszyn, Yichuan Shen, ttrinidad, Arad Alvand, Anthony Luzquiños, Slava Pavlutin, Dmitry and 5 moreDecided to stop being a lurker and start joining in the Typescript community alittle more hopefully this contribution helps put this Union -> Tuple problem to rest untill Typescript hopefully gives us some syntax sugar.
This is my "N" depth Union -> Tuple Converter that maintains the order of the Union
// add an element to the end of a tuple type Push<L extends any[], T> = ((r: any, ...x: L) => void) extends ((...x: infer L2) => void) ? { [K in keyof L2]-?: K extends keyof L ? L[K] : T } : never export type Prepend<Tuple extends any[], Addend> = ((_: Addend, ..._1: Tuple) => any) extends (( ..._: infer Result ) => any) ? Result : never; // export type Reverse<Tuple extends any[], Prefix extends any[] = []> = { 0: Prefix; 1: ((..._: Tuple) => any) extends ((_: infer First, ..._1: infer Next) => any) ? Reverse<Next, Prepend<Prefix, First>> : never; }[Tuple extends [any, ...any[]] ? 1 : 0]; // convert a union to an intersection: X | Y | Z ==> X & Y & Z type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never // convert a union to an overloaded function X | Y ==> ((x: X)=>void) & ((y:Y)=>void) type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>; // returns true if the type is a union otherwise false type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true; // takes last from union type PopUnion<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? A : never; // takes random key from object type PluckFirst<T extends object> = PopUnion<keyof T> extends infer SELF ? SELF extends keyof T ? T[SELF] : never; type ObjectTuple<T, RES extends any[]> = IsUnion<keyof T> extends true ? { [K in keyof T]: ObjectTuple<Record<Exclude<keyof T, K>, never>, Push<RES, K>> extends any[] ? ObjectTuple<Record<Exclude<keyof T, K>, never>, Push<RES, K>> : PluckFirst<ObjectTuple<Record<Exclude<keyof T, K>, never>, Push<RES, K>>> } : Push<RES, keyof T>; /** END IMPLEMENTATION */ type TupleOf<T extends string> = Reverse<PluckFirst<ObjectTuple<Record<T, never>, []>>> interface Person { firstName: string; lastName: string; dob: Date; hasCats: false; } type Test = TupleOf<keyof Person> // ["firstName", "lastName", "dob", "hasCats"]Reacted by James Greenaway, Henning Pohlmeyer, Kevin Ryan, Piotr Kapera, Max Sysoev, Takaaki KITANO, Evg, Chris, codelovesme, Vakhurin Sergei and 27 moreReacted by James Greenaway, Max Sysoev, codelovesme, AlvinThorn008, Will Frew and kato-yoshiharuReacted by m0sk1tReacted by Evg, Chris, Vakhurin Sergei, Ilya Sergeev, Nathan, Danilo Barros, AlvinThorn008, Anurag Hazra, hiroya iizuka, kato-yoshiharu and 1 moreReacted by yossarian and kato-yoshiharu- changed the title
[-]Type manipulations: union to tuple, tuple to union[/-][+]Type manipulations: union to tuple[/+]on Feb 28, 2019 Finally removed the bit about union to tuple, since there are plenty of ways to do that now (there weren’t when this suggestion was first made). Also, much thanks to Shanon Jackson (@ShanonJackson), that looks awesome and I will have to try that. Still, that’s a lot of code for this; sugar would be rather appreciated here. Or at least a built-in type that comes with Typescript, so that doesn’t have to be re-implemented in every project.
Reacted by thetumperKevin Ryan (@krryan) A solution of that size should be published as an NPM package, IMO.
Worth noting: The
TupleOftype provided by Shanon Jackson (@ShanonJackson) only supports string unions, so it's not a universal solution by any means.Alec Larson (@aleclarson) Yes, but installing a dependency is, to my mind, still “reimplementing” it, at least in the context here. Sure, an NPM package is superior to copying and pasting that code around. But I don’t think either should be necessary for this. It’s a language construct that is broadly useful to all Typescript developers, in my opinion, so it should just be available (and quite possibly be implemented more easily within tsc than as a type in a library).
Anyway, good point about the string limitation; that’s quite severe (I might still be able to use that but it’s going to take some work since I’ll have to get a tuple of my discriminants and then distribute those appropriately, but I think it will work for my purposes).
Reacted by Jacob Bergholtzdragomirtitian commented
on Feb 28, 2019 ContributorMore actionsShanon Jackson (@ShanonJackson)
I fear that while this solution works, it is very compiler unfriendly .. I added just a couple more keys to the object and when I hovered over it the language server got up to 100% CPU usage, ate up 3GB of RAM and no tooltips ever show up.
interface Person { firstName: string; lastName: string; dob: Date; hasCats: false; hasCats1: false; hasCats2: false; hasCats3: false; hasCats4: false; } type Test = TupleOf<keyof Person> // tool tip never shows up HUGE amount of RAM and CPU Used,
Reacted by Alec Larson, Piotr Witek, Russell Dempsey, Artiom Neganov, Anurag Hazra, Dan Vanderkam, Victor Malov, 仿生狮子, Alexey Berezin and Sean AlunniReacted by Morteza Tourani, David Sherret, Oleh Misarosh, Jiacheng, Victor Malov and Graham Ashton- addedSuggestionAn idea for TypeScriptAn idea for TypeScriptNeeds More InfoThe issue still hasn't been fully clarifiedThe issue still hasn't been fully clarifiedand removedNeeds InvestigationThis issue needs a team member to investigate its status.This issue needs a team member to investigate its status.
on Feb 28, 2019 RyanCavanaugh commented
on Feb 28, 2019 MemberMore actionsSorry this has been stuck in Need Investigation so long!
My primary question is: What would this be useful for? Hearing about use cases is really important; the suggestion as it stands seems like an XY problem situation.
Secondary comments: This suggestion could almost certainly never happen; problems with it are many.
First, union order is not something we can ever allow to be observable. Internally, unions are stored as a sorted list of types (this is the only efficient way to quickly determine relationships between them), and the sort key is an internal ID that's generated incrementally. The practical upshot of this is that two extremely similar programs can generate vastly different union orderings, and the same union observed in a language service context might have a different ordering than when observed in a commandline context, because the order in which types are created is simply the order in which they are checked.
Second, there are basic identities which are very confusing to reason about. Is
tupleof T | ( U | V )[T, U | V]or[T, U, V]? What about this?// K always has arity 2? type K<T, U> = tupleof (T | U); // Or Q has arity 3? Eh? type Q = K<string, number | boolean>;
There are more problems but the first is immediately fatal IMO.
Reacted by Alec Larson, Titian Cernicova-Dragomir, Mateja Petrovic, Joe Calzaretta, Dan Wood, Darryl Noakes and Jimmy Zhening LuoReacted by ZpdDG4gta, Karol Majewski, Mathieu Fedrigo and Artur Klesun78 remaining items
- added 2 commits that reference this issue
on Oct 12, 2023 - added a commit that references this issue
on Feb 3, 2024 type MyTypeUnion = MyType1 | MyType2[] type UnionToTuple = ... type ConvertedTuple = UnionToTuple<MyTypeUnion> type FirstType = ConvertedTuple[1] // MyType1 type SecondType = ConvertedTuple[2] // MyType2[]
How can I do the
UnionToTupletype definition?How can I do the
UnionToTupletype definition?You can’t, it’s not possible, that’s why this issue is closed. Unions are unordered, tuples are ordered—a tuple requires information that a union hasn’t got. If possible—it isn’t always—you may want to write your definition as an array to begin with, and then tuple-to-union is easy:
export const tuple = [ // … ] as const; export type Union = typeof tuple[number];
Unfortunately, this often isn’t workable when your union isn’t hard-coded, but derived from other types.
There is a way you can have the compiler check that your tuple contains all the elements in the union, nothing else, and no duplicates, but it’s a fairly tedious amount of boilerplate:
This bit you can re-use:
/** The unique elements of the input tuple, in order. Only works with `readonly` tuples. */ export type SetTuple<T extends readonly any[]> = _SetTuple<T>; type _SetTuple<T extends readonly any[], A extends readonly any[] = readonly []> = T extends readonly [infer H, ...infer R] ? H extends A[number] ? _SetTuple<R, A> : _SetTuple<R, readonly [...A, H]> : A;
Here are your union and tuple definitions, and compile-time testing that the tuples match the union.
import { SetTuple } from 'set-tuple'; // wherever SetTuple is exported from export type TheUnion = 'foo' | 'bar'; /** A correct tuple, in the same order as the union */ export const validTuple = ['foo', 'bar'] as const; ((): SetTuple<typeof validTuple> => validTuple); // no error: has no duplicates ((): readonly TheUnion[] => validTuple); // no error: does not include anything but 'foo' and 'bar' ((union: TheUnion): typeof validTuple[number] => union); // no error: includes both 'foo' and 'bar' /** A correct tuple, in the opposite order as the union (doesn't matter) */ export const alsoValidTuple = ['bar', 'foo'] as const; ((): SetTuple<typeof alsoValidTuple> => alsoValidTuple); // no error: has no duplicates ((): readonly TheUnion[] => alsoValidTuple); // no error: does not include anything but 'foo' and 'bar' ((union: TheUnion): typeof alsoValidTuple[number] => union); // no error: includes both 'foo' and 'bar' /** A wrong tuple, for several reasons */ export const invalidTuple = ['bar', 'bar', 'baz'] as const; ((): SetTuple<typeof invalidTuple> => invalidTuple); // error: duplicate 'bar' elements ((): readonly TheUnion[] => invalidTuple); // error: 'baz' is not in the union ((union: TheUnion): typeof invalidTuple[number] => union); // error: 'foo' is not in the tuple
Here, we use a trio of never-invoked anonymous function expressions, which don’t pollute the namespace and have minimal effect on the run time, and allow us to include some statements that the compiler will check for us, so we can confirm the tuple is what we want it to be. As you can see, you have to include all three statements for each tuple you want to check. (You could combine the first two, technically, but that’s not a lot of savings and it makes the error messages harder to read.) I tried writing a utility type or even utility function that would save on this boilerplate, but everything I came up with had the same two flaws: you still needed a bunch of boilerplate to cause it to actually error when it’s supposed to, and the error messages are impossible to read.
Still, if you only have a few such tuples, and they really must cover the union, this is a safe solution.
Reacted by Daniel Arthur Gallaghertype TuplifyUnion<U extends string> = { [S in U]: // for each variant in the union Exclude<U, S> extends never // remove it and.. ? [S] // ..stop recursion if it was the last variant : [...TuplifyUnion<Exclude<U, S>>, S] // ..recur if not }[U] // extract all values from the object type fs = TuplifyUnion<'1' | '2' | '3'>; //equal to type fs = ["3", "2", "1"] | ["2", "3", "1"] | ["3", "1", "2"] | ["1", "3", "2"] | ["2", "1", "3"] | ["1", "2", "3"]
Ok, yes, that works, as long as the union is very small. I tested my implementation with a 114-member union (letters A-Z and a-z, digits 0-9, Greek letters Α-Ω and α-ω, and 'foo', 'bar', and 'baz'), which was no problem. In my testing,
TuplifyUnionstarts to slow down at 9 union members, and at 10 evaluating it in VS Code Intellisense times out and it’s typed asany(and I’m frankly impressed as hell that Typescript got that far, since the resulting type for 9 was a union of over 300,000 tuples).TuplifyUnionalso had significant performance problems that affected my entire editor with larger unions.I also came up with a less-boilerplate-y way to create a re-usable type to check a tuple covers a union:
export type TestTupleExactlyCoversUnion<T extends readonly any[], U extends string | number | bigint | boolean | null | undefined> = [T, U] extends [SetTuple<T> & readonly U[], T[number]] ? true : Split<T extends any ? 'string--------------------------------------------' : never>; type Split<T> = T extends `${infer H}${infer Rest}` ? readonly [H, ...Split<Rest>] : readonly []; { type Good = TestTupleExactlyCoversUnion<typeof validTuple, TheUnion>; type Bad = TestTupleExactlyCoversUnion<typeof invalidTuple, TheUnion>; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Type instantiation is excessively deep and possibly infinite. ts(2589) }
Putting the types in the brackets avoids polluting the namespace, so the names only have to be unique within that context, which is good. You still have to assign names to them, which is annoying. The error message is also much less useful: you just get “Type instantiation is excessively deep and possibly infinite,” which is not terribly helpful (also, if you have a union too large for
SetTuple—I didn’t find its limit but I’m pretty sure it’s got one—you’ll get exactly the same error and won’t be able to distinguish “my union is too large” from “my tuple is wrong.” I still think the separate trio of statements is superior, but this does work.ernestostifano commented
on Nov 12, 2024 More actionsFor anyone needing to do
uniontotupleconversions with generic types, not only strings, I created the followingTUnionToTupleutility, inspired on some of the answers. It is limited to primitives, but does the trick.enum ETypeKey { Null = 'null', String = 'string', Number = 'number', Boolean = 'boolean', Symbol = 'symbol', Date = 'date' } type TMap<T> = T extends null ? typeof ETypeKey.Null : T extends string ? ETypeKey.String : T extends number ? ETypeKey.Number : T extends boolean ? ETypeKey.Boolean : T extends symbol ? ETypeKey.Symbol : T extends Date ? ETypeKey.Date : never; type TReverseMap<TKey> = TKey extends ETypeKey.Null ? null : TKey extends ETypeKey.String ? string : TKey extends ETypeKey.Number ? number : TKey extends ETypeKey.Boolean ? boolean : TKey extends ETypeKey.Symbol ? symbol : TKey extends ETypeKey.Date ? Date : never; type TUnionToKeys<T> = T extends unknown ? TMap<T> : never; // SEE: https://github.com/microsoft/TypeScript/issues/13298#issuecomment-2373724144 type TKeysToTuple<T extends string> = { [k in T]: Exclude<T, k> extends never // FOR EACH VARIANT IN THE UNION // REMOVE IT AND... ? [k] // ...STOP RECURSION IF IT WAS THE LAST VARIANT : [...TKeysToTuple<Exclude<T, k>>, k]; // ...RECURSE IF NOT }[T]; // EXTRACT ALL VALUES FROM THE OBJECT type TUnmappedTuple<T extends readonly unknown[]> = { [K in keyof T]: TReverseMap<T[K]>; }; type TRevert<T> = T extends unknown[] ? TUnmappedTuple<T> : never; type TUnionToTuple<T> = TRevert<TKeysToTuple<TUnionToKeys<T>>>; type TTest1 = TUnionToTuple<number | string>; // [number, string] | [string, number] type TTest2 = TUnionToTuple<boolean>; // [boolean] type TTest3 = TUnionToTuple<number | string | Date>; // [Date, number, string] | [number, Date, string] | [Date, string, number] | [string, Date, number] | [number, string, Date] | [string, number, Date]
What rtritto wanted to do, should be possible with this, as long as we are talking primitives.
Decided to stop being a lurker and start joining in the Typescript community alittle more hopefully this contribution helps put this Union -> Tuple problem to rest untill Typescript hopefully gives us some syntax sugar.
This is my "N" depth Union -> Tuple Converter that maintains the order of the Union
// add an element to the end of a tuple type Push<L extends any[], T> = ((r: any, ...x: L) => void) extends ((...x: infer L2) => void) ? { [K in keyof L2]-?: K extends keyof L ? L[K] : T } : never export type Prepend<Tuple extends any[], Addend> = ((_: Addend, ..._1: Tuple) => any) extends (( ..._: infer Result ) => any) ? Result : never; // export type Reverse<Tuple extends any[], Prefix extends any[] = []> = { 0: Prefix; 1: ((..._: Tuple) => any) extends ((_: infer First, ..._1: infer Next) => any) ? Reverse<Next, Prepend<Prefix, First>> : never; }[Tuple extends [any, ...any[]] ? 1 : 0]; // convert a union to an intersection: X | Y | Z ==> X & Y & Z type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never // convert a union to an overloaded function X | Y ==> ((x: X)=>void) & ((y:Y)=>void) type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>; // returns true if the type is a union otherwise false type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true; // takes last from union type PopUnion<U> = UnionToOvlds<U> extends ((a: infer A) => void) ? A : never; // takes random key from object type PluckFirst<T extends object> = PopUnion<keyof T> extends infer SELF ? SELF extends keyof T ? T[SELF] : never; type ObjectTuple<T, RES extends any[]> = IsUnion<keyof T> extends true ? { [K in keyof T]: ObjectTuple<Record<Exclude<keyof T, K>, never>, Push<RES, K>> extends any[] ? ObjectTuple<Record<Exclude<keyof T, K>, never>, Push<RES, K>> : PluckFirst<ObjectTuple<Record<Exclude<keyof T, K>, never>, Push<RES, K>>> } : Push<RES, keyof T>; /** END IMPLEMENTATION */ type TupleOf<T extends string> = Reverse<PluckFirst<ObjectTuple<Record<T, never>, []>>> interface Person { firstName: string; lastName: string; dob: Date; hasCats: false; } type Test = TupleOf<keyof Person> // ["firstName", "lastName", "dob", "hasCats"]this is beautiful, and allowed me to convert typescript enums to truple of values, thing of beauty thank you.
I agree that it is not a good idea to allow conversion of fundamentally unordered unions to ordered tuples, but the thing is that javascript quite often uses arrays for things like object keys, which have no order. If possible, it might be a good idea to add a new tuple type that has items, possibly mutable of the same ones, but could be in any order. Tell me if that would not work, but it seems to me like a good addition.
RyanCavanaugh commented
on Oct 6, 2025 MemberMore actionsI can't think of anything useful you could do with that type that you can't already do with
(keyof T)[]Well, you could specify “this is an unordered list of these items, each exactly once,” which was the goal of this topic.
(keyof T)[]could be missing keys or have duplicate entries. And I still think it’d be a useful thing to be able to do, in theory. Defining things as runtimeconsts first and extracting types from those, however, works about as well, so not really useful enough to bother with.Reacted by Ryan Cavanaugh and Sam A. Horvath-HuntRyanCavanaugh commented
on Oct 6, 2025 MemberMore actionsWell, you could specify “this is an unordered list of these items, each exactly once,”
Right. This makes sense to me from a write perspective (you can only initialize this type with a very specific value), but from a read perspective I can't think of an operation that's valid on
[A, B, C, D, E, ...]that isn't valid on[B, C, D, E, ...]except for the very specific case of reconstructing an object by key/value pair (which is really only ever done generically once and thus never benefits from a concrete representation of that type).Well that’s just the thing: the people interested in this don’t care about the order, precisely because it rarely matters, but TS requires that they specify one, which means TS can’t extract an array from an inherently unordered union. If
unordered [A, B, C, D, E, …]existed, then you could imagine atype UnionToTuple<A | B | C | D | E | …>that evaluates to it. But without the data structure to hold it, that’s impossible.Reacted by Ryan CavanaughRyanCavanaugh commented
on Oct 6, 2025 MemberMore actionsAgreed; the question is what operations are gained by
unordered [A, B, C]? To me the options are:- Put it in a write position (OK but comparatively rare)
- Something you could already legally do with
Array<A | B |C> - Something ill-advised like
type Order<T> = T extends unordered infer U ? U : neverwhich would immediately re-introduce the problem we're trying to avoid in the first place
A suggestion to create a runtime array of union members was deemed out of scope because it would not leave the type system fully erasable (and because it wouldn't be runtime complete, though that wasn't desired). This suggestion is basically a variant of that one that stays entirely within the type domain, and thus stays erasable.
The suggestion is for a keyword similar to
keyofthat, when given a union type, would result in a tuple type that includes each possibility in the union.Combined with the suggestion in this comment to instead implement a codefix to create the array literal, this could be used to ensure that 1. the array was created correctly to begin with, and 2. that any changes to the union cause an error requiring the literal array to be updated. This allows creating test cases that cover every possibility for a union.
Syntax might be like this:
Some issues I foresee:
I don't know what ordering is best (or even feasible), but it would have to be nailed down in some predictable form.
Nesting is complicated.
I expect generics would be difficult to support?
Inner unions would have to be left alone, which is somewhat awkward. That is, it would not be reasonable to turn
Wrapper<Foo|Bar>into[Wrapper<Foo>, Wrapper<Bar>]even though that might (sometimes?) be desirable. In some cases, it’s possible to use conditional types to produce that distribution, though it has to be tailored to the particularWrapper. Some way of converting back and forth betweenWrapper<Foo|Bar>andWrapper<Foo>|Wrapper<Bar>would be nice but beyond the scope of this suggestion (and would probably require higher-order types to be a thing).My naming suggestions are weak, particularly
tupleof.NOTE: This suggestion originally also included having a way of converting a tuple to a union. That suggestion has been removed since there are now ample ways to accomplish that. My preference is with conditional types and
infer, e.g.ElementOf<A extends unknown[]> = A extends (infer T)[] ? T : never;.