Home > Software design >  Typescript union type to object type
Typescript union type to object type

Time:11-18

Is it possible to translate a type of this kind:

export type UnionType = {
    element:
        | { $case: 'a'; a: number }
        | { $case: 'b'; b: string }
        | { $case: 'c'; c: boolean };
};

to a type of a kind:

type OneOfConverter<T> = any; // TODO

type ObjectType = OneOfConverter<UnionType>;

// type ObjectType = {
//     a: number;
//     b: string;
//     c: boolean;
// };

Typescript playground

CodePudding user response:

My suggestion here would be:

type OneOfConverter<T extends { element: { $case: PropertyKey } }> = {
    [U in T["element"] as U["$case"]]: U["$case"] extends keyof U ? U[U["$case"]] : never
}

It uses key remapping in mapped types to iterate over the union members of T['element'] (an indexed access type corresponding to the type of the element property of the type T passed in) and put each member of that union into a type parameter U. Then the key we want is U["$case"].

For each value we pretty much want to index into U with that key, so U[U["$case"]], but the compiler doesn't know if that member really will exist (maybe you pass in { $case: "z" } and the z property doesn't appear for some reason). So we have to address its concern by first doing the conditional type check U["$case"] extends keyof U, which compares the desired key to the keys of U. If it exists, we grab the property value as U[U["$case"]]. If it doesn't, then we return never for want of anything better.

Let's make sure it works:

type ObjectType = OneOfConverter<UnionType>;
// type ObjectType = {
//     a: number;
//     b: string;
//     c: boolean;
// };

Looks good!

Playground link to code

  • Related