Let's say I have a union type:
type UnionStr = 'foo' | 'bar' | 'baz';
I wonder if there is a type that infers the result as following:
type Join<T, Separator> = {...}
type Test = Join<UnionStr, ','> // -> foo,bar,baz
Let's say I have a union type:
type UnionStr = 'foo' | 'bar' | 'baz';
I wonder if there is a type that infers the result as following:
type Join<T, Separator> = {...}
type Test = Join<UnionStr, ','> // -> foo,bar,baz
You can get close, but there's a problem: unions don't really have an order you can rely on. The compiler is fully within its rights to rearrange if it wants to. There's also some other weird cases with unions as well. I recommend looking at this answer by Jcalz about how to turn a union into a tuple for more details and pitfalls: https://stackoverflow.com/a/55128956/3794812
If we can change your requirements so the starting point is a tuple (eg, ['foo', 'bar', 'baz']
) we'll be able to do it with no problem. But if you must start with a union, then we have to do a hacky and unreliable conversion from union to tuple. Which you shouldn't do. Seriously, see Jcalz's answer for all the reasons why you shouldn't do it.
The plan is to take the tuple of strings and recursively join them together. To do that, i need to be able to break off the head ("foo"
, for the first step) and the tail (["bar", "baz"]
). The head is easy, that's just T[0]
, where T
is the tuple. For the tail, we have this utility type:
type Tail<T extends string[]> = T extends [string, ...infer TailType] ? TailType : never;
The following type takes in a string (H
for "head"), an array of strings (T
for "tail"), and a string for the separator. If the array has exactly one string in it, then the result is just to join them together with the separator, as in ${H}${Separator}${T[0]}
. If there's more than one string, we need to recursively repeat the process, joining the head with whatever we get by joining the tail together
type JoinRecursive<H extends string, T extends string[], Separator extends string> =
Tail<T> extends [string, ...string[]]
? `${H}${Separator}${JoinRecursive<T[0], Tail<T>>}`
: `${H}${Separator}${T[0]}`
The recursive generic expects it to already be split into a head and a tail, so for convenience, i'll make a Join type that can take in the string tuple directly, and then will do that first split:
type Join<T extends string[], Separator extends string> =
Tail<T> extends string[]
? JoinRecursive<T[0], Tail<T>, Separator>
: T[0];
And here's how it then gets used:
type UnionTuple = ['foo', 'bar', 'baz'];
// If you must start from a union, the best that can be done is
// Jcalz's TuplifyUnion hack. But don't do this.
// type UnionStr = 'foo' | 'bar' | 'baz';
// type UnionTuple = TuplifyUnion<UnionStr>;
type Test = Join<UnionTuple, ','> // 'foo,bar,baz'
type Test2 = Join<UnionTuple, '*'> // 'foo*bar*baz'