typescript - Transformă uniunea într-o hartă


Să începem cu acest tip dat:

type Union =
  { type: 'A', a: string } |
  { type: 'B', b: number }

Vreau să ajungem aici:

type MappedUnion = {
  A: { type: 'A', a: string }
  B: { type: 'B', b: number }
}

Iată niște pseudo-cod:

type MappedUnion<item in Union> = {[ i: item['type'] ]: item}

Răspuns #1

Cred că poți face următoarele? Nu am testat exhaustiv, dar pare să funcționeze.

type Union =
  { type: 'A', a: string } |
  { type: 'B', b: number }

type MappedUnion = {[P in Union['type']]: Union & {type: P}};

const v: MappedUnion = {
  'A': {type: 'A', a: "hello"},
  'B': {type: 'B', b: 3},
}

Din păcate, nu funcționează prea bine; dacă o fac

type MappedUnionTarget = {
  A: { type: 'A', a: string }
  B: { type: 'B', b: number }
}

function cvt1(x: MappedUnion): MappedUnionTarget {
  return x;
}

apoi primesc Type '({ type: "A"; a: string; } & { type: "A"; }) | ({ type: "B"; b: number; } & { type: "A"; })' nu este atribuibil tipului '{ type: "A"; a: string; }'. Am crezut că typescript va putea să deducă că ({ type: "B"; b: number; } & { type: "A"; }) era același cu never.

Dacă ești dispus să folosești Typescript 2.8 (care, în momentul scrierii, nu a fost lansat și este disponibil dacă faci git pull din repo-ul TypeScript, și care are suport pentru operatorul condițional ? : la nivel de tip) atunci se pare că poți face

type FilterType<T, K> = (T extends { type: K } ? T : never);

type MappedUnion = {[P in Union['type']]: FilterType<Union, P>};

pentru că FilterType<T, K> este ceea ce speram să se comporte T & {type: K}.