Reputation: 107
I'd like to transform a type to a discriminated union type:
type Current = {
A : number,
B : string,
}
to
type Target= {
type: 'A',
value: number
}
| {
type: 'B',
value: string
}
So that I can discriminate the result.
function handle(result: ToTarget<Current>) {
switch(result.type){
case 'A':
return result.value // number
case 'B':
return result.value // string
}
}
The closest I got was:
type ToTargetA<U> = { code: keyof U, value : U[keyof U] }
// code and value are not in sync so code is 'A | B' and value is 'number | string' but discriminating on code does not narrow value.
type ToTargetB<U, K extends keyof U = any> = { code: keyof K, value : U[K] }
// requires me to specify K as a generic parameter which is not what I want.
I tried several conditional type expressions but couldn't get much closer.
Upvotes: 0
Views: 1656
Reputation: 329398
Here's one way to do it:
type ToDiscriminatedUnion<T, KK extends PropertyKey, VK extends PropertyKey> =
{ [K in keyof T]: { [P in KK | VK]: P extends KK ? K : T[K] } }[keyof T];
You can verify that it produces the type you want:
type Target = ToDiscriminatedUnion<Current, 'type', 'value'>;
/* type Target = {
type: "A";
value: number;
} | {
type: "B";
value: string;
} */
The approach here is to build a mapped type with the same keys K
as in your original object type T
, but whose values are the {type: T, value: T[K]}
types you want in the discriminated union. This type ends up becoming {A: {type: "A", value: number}, B: {type: "B". value: string}}
.
Then we can look up the union of its property values by indexing into it with [keyof T]
, producing the desired {type: "A", value: string} | {type: "B", value: number}
discriminated union.
The only extra thing I did there was to make it so you could specify what keys to give to the original key names (KK
, being "type"
in your case) and the original value names (VK
, being "value"
in your case). If you don't want to change that ever, you can hardcode it:
type ToDiscriminatedUnion<T> =
{ [K in keyof T]: { type: K, value: T[K] } }[keyof T];
type Target = ToDiscriminatedUnion<Current>;
Upvotes: 5
Reputation: 107
This works, but wondering if there is a cleaner/better solution.
type DistributedProperties<T> = { [P in keyof T] : { code: P, value: T[P]} }
type Union<T> = DistributedProperties<T>[keyof DistributedProperties<T>]
Upvotes: 0