So there is a trick I found in typescript to turn an object type into a discriminated union by mapping the type to key-value pair then creating a type that can be any value in the map using the keyof
type. Here is a simple example:
type SourceType =
{
foo: number,
bar: string
};
type MapWithKey<T> = {[P in keyof T]: { key: P, value: T[P] }}
type DescriminatedUnion = MapWithKey<SourceType>[keyof SourceType];
//The DescriminatedUnion now has the following type
DescriminatedUnion ≡ {key:"foo",value:string} | {key:"bar",value:number}
This is very useful if you want to specify a very large discriminated union however when you try to make this construct fully generic you end up with a different type.
type MakeDescriminatedUnion<T> = MapWithKey<T>[keyof T];
type DescriminatedUnion = MakeDescriminatedUnion<SourceType>
//The DescriminatedUnion now has the followin type
DescriminatedUnion ≡ {key:"foo"|"bar",value:number|string}
This should be the same type but for some reason it isn't. I have tried to look through the typescript documentation to find some reasoning for this behavior however I cannot. Does anyone know the reasoning behind this difference? Or even better does anyone know a way to get around this behavior and make it fully generic?
Yes, this issue has bitten me and quite a few others. It's amazing that, as @artem mentions, a fix slated for TypeScript 2.6 was introduced just today!
Meanwhile, for those of us stuck in TypeScript 2.4-land, there is a workaround using default generic type parameters:
type MakeDiscriminatedUnion<T, M extends MapWithKey<T> = MapWithKey<T>> = M[keyof T];
type DiscriminatedUnion = MakeDiscriminatedUnion<SourceType> // okay now
The actual value of M
doesn't get evaluated until you use MakeDiscriminatedUnion<SourceType>
, so the compiler has no chance to "simplify" M[keyof T]
the way it does above.
Anyway, it's your choice whether to use the workaround or wait for TypeScript 2.6. Hope that helps. Good luck!