Home > Enterprise >  Typescript, how to turn a discrimated union into an object?
Typescript, how to turn a discrimated union into an object?

Time:11-11

If the discrimated union is defined as follows:

enum KEYS {
  A= 'a',
  B= 'b'
  ...
}

type TUnion =
  | { keyType: KEYS.A; items: AType[]
  | { keyType: KEYS.B; items: BType[]
  ...

How can I define the type for an object where the key is a keyType and the value is the matching ìtems`, without doing it all manually?

The manual approach seems to work but repeats the key-to-possible values relation:

type TObj = {
  [KEYS.A]?: AType[]
  [KEYS.B]?: BType[]
  ...
}

CodePudding user response:

Here's a great utility type to take a discriminated union and two of its properties and transform it into a map of the keys to values:

type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? { [K in keyof I]: I[K] } : never;

type FromDiscriminatedUnion<U, K extends keyof U, V extends keyof U> = UnionToIntersection<U extends U ? {
    [_ in U[K] & PropertyKey]: U[V];
} : never>;

We distribute over the union and get the selected key and map it to a value. Since this produces another union, we need to turn this union into an intersection.

Lastly, if you want all the properties to be optional, wrap the output with Partial:

type Result = Partial<FromDiscriminatedUnion<TUnion, "keyType", "items">>;

Playground

CodePudding user response:

Similar idea to caTS's, but a bit simpler: tsplayground

enum KEYS {
    A = "a",
    B = "b",
}

type AType = number;
type BType = string;

type TUnion = { keyType: KEYS.A; items: AType[] } | { keyType: KEYS.B; items: BType[] };

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type ToKeyValue<U extends TUnion> = U extends TUnion ?  { [key in U['keyType']]: U['items']} : never;

type mapped = UnionToIntersection<ToKeyValue<TUnion>>


type optionalMapped = Partial<UnionToIntersection<ToKeyValue<TUnion>>>

CodePudding user response:

It makes more sense to define your types the other way around: declare TObj first, and then you can derive your discriminated union from it.

type TObj = {
  [KEYS.A]?: AType[]
  [KEYS.B]?: BType[]
}

type TUnion = {
  [K in KEYS]: {keyType: K, items: Exclude<TObj[K], undefined>}
}[KEYS]

// type TUnion = {
//     keyType: KEYS.A;
//     items: AType[];
// } | {
//     keyType: KEYS.B;
//     items: BType[];
// }

Playground Link

  • Related