Reputation: 1703
How do you create a Typescript string type, which includes values from a union type AND is separated by commas?
I think this doesn't exist, but I'm asking anyway...
type AcceptableVal = 'cool' | 'wow' | 'biz' | 'sweet' | 'yowza' | 'fantastic';
type Amaze = // how do you define this?;
const dabes: Amaze = 'cool,wow';
const fancy: Amaze = 'biz,wow,fantastic';
const hachiMachi: Amaze = 'yowza,biz,sweet';
Upvotes: 17
Views: 6512
Reputation: 383
with new template string feature of ts4.1 you can do this using following utils
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
? I
: never;
type UnionToOvlds<U> = UnionToIntersection<U extends any ? (f: U) => void : never>;
type PopUnion<U> = UnionToOvlds<U> extends (a: infer A) => void ? A : never;
type UnionConcat<U extends string, Sep extends string> = PopUnion<U> extends infer SELF
? SELF extends string
? Exclude<U, SELF> extends never
? SELF
:
| `${UnionConcat<Exclude<U, SELF>, Sep>}${Sep}${SELF}`
| UnionConcat<Exclude<U, SELF>, Sep>
| SELF
: never
: never;
example
type Keys = "a" | "b" | "c";
type Test = UnionConcat<Keys, ",">
// Test = "a" | "b" | "c" | "a,b" | "a,c" | "b,c" | "a,b,c"
type Test2 = UnionConcat<Keys, "-">
// Test2 = "a" | "b" | "c" | "a-b" | "a-c" | "b-c" | "a-b-c"
Upvotes: 11
Reputation: 187004
You can now use string template literal types to define a pattern like:
type S = 'cool' | 'wow' | 'biz' | 'sweet' | 'yowza' | 'fantastic';
type Test = `${S},${S}`
What you still can't do is make this infinitely extensible, like an array. To make this work typescript generates every single combination as a union. But for small lists like this it can work.
For example:
type S = 'cool' | 'wow' | 'biz' | 'sweet' | 'yowza' | 'fantastic';
type Amaze =
| `${S}`
| `${S},${S}`
| `${S},${S},${S}`
| `${S},${S},${S},${S}`
| `${S},${S},${S},${S},${S}`
| `${S},${S},${S},${S},${S},${S}`
If you hover over Amaze
, you will see the type reported as:
type Amaze = S | "cool,cool" | "cool,wow" | "cool,biz" | "cool,sweet"
| "cool,yowza" | "cool,fantastic" | "wow,cool" | "wow,wow"
| "wow,biz" | "wow,sweet" | "wow,yowza" | "wow,fantastic"
| ... 55967 more ...| "fantastic,fantastic,fantastic,fantastic,fantastic,fantastic"
Notice that ... 55967 more ...
. Amaze is now a union with over fifty five thousand possible values. This may affect performance in your IDE at this point. And if you add the version that takes 7 strings you'll get a type error:
Expression produces a union type that is too complex to represent.(2590)
Eventually typescript cuts you off for performance sake. But again, for small lists, this may be viable.
You can't.
Typescript can type string
s which can have any content, or can it can type exact strings like "cool"
or "wow"
. But typescript will never know about if a string contains certain characters.
The only way to make this work would be to store these as an array instead:
type AmazeArray = AcceptableVal[];
const dabes: AmazeArray = ['cool', 'wow'];
Upvotes: 17
Reputation: 145890
You can sort of do this with template literal types.
type GoodDog = 'ruger' | 'kim' | 'abbie' | 'bo' | 'jasper';
type DogList = `${ GoodDog }` |
`${ GoodDog },${ GoodDog }` |
`${ GoodDog },${ GoodDog },${ GoodDog }`;
const favoriteDogs1: DogList = 'kim,abbie,ruger'
This would support dog lists up to 3, and allow duplicates. You could add more as needed.
Unfortunately if you try to make this recursive it won't allow it. I think theoretically it may be possible to make a type that would be recursive with a 'tail condition' but I couldn't immediately figure it out.
A better way may be to use as const
on an array (NOT a type).
const Colors = ['red', 'blue', 'green', 'orange'] as const;
This is a runtime variable array, so you can display it in your UI - add it to a dropdown etc.
Then you need to extract the 'type' of this constant array (which is only possible because we made it const).
type ExtractArrayType<T> = T extends ReadonlyArray<infer U> ? U : never;
type ColorType = ExtractArrayType<typeof Colors>;
This will give us:
ColorType = 'red' | 'blue' | 'green' | 'orange;
Note that if we hadn't defined the original array with as const
then ColorType
would just be string
.
We can now use this type and create an array;
const colorList: ColorType[] = ['red', 'blue'];
This approach is more useful for most runtime cases, although occasionally it would be nice to define something like red,blue
and have that type checked.
Upvotes: 1