1

I tried to write the following TypeScript code and the fact that it's kinda working is raising some questions. It's "kinda" working because the type of the constructed "union" property is indeed the union of the record values. Record keys are ignored.

export type Unificator<T extends Record<string, unknown>> = { [P in keyof T as "union"]: T[P] }["union"];

let u: Unificator<{
  a: { foo: "bar" },
  b: { bar: "baz" },
  c: { answer: 42 }
}>;

u = { foo: "bar" }; // Valid.
u = { bar: "baz" }; // Valid.
u = { answer: 42 }; // Valid.

The first three questions are:

  • Is this a legitimate use of mapped types or am I doing something weird?
  • Is there a more orthodox way to union an arbitrary number of types together?
  • If the above code is indeed legit, is there a way to avoid that temporary "union" property?

But the most important question for me is another one:

  • Is there a way to "intersect" an arbitrary number of types?

I want to do the following.

export type Intersector<T extends Record<string, unknown>> = /* HELP PLZ! */;

let i: Intersector<{
  a: { foo: "bar" },
  b: { bar: "baz" },
  c: { answer: 42 }
}>;

i = {
  foo: "bar",
  bar: "baz",
  answer: 42
}; // Valid.
damix911
  • 4,165
  • 1
  • 29
  • 44
  • Simplest way to create a union of object property types is probably `T[keyof T]` - https://www.typescriptlang.org/play?#code/KYDwDg9gTgLgBDAnmYcCSA7GwoGdgDGM0APACpyjYYAmucASodDSbjFAJYYDmANHACuGANYYIAdwwA+aXAC8cMgG0RwRBABmSgLoBYAFCGANsHicAXOiw58RUgG9DcOAEMrDuJogQrAIgAjVyg-OABfPmc4AI9o4P8ggC9QiKiCWNcMXAkcKwAWACZwwzDpAG5DQ04FOCcDF29fOEDgv0j6uKgE12T2l0zs3LhCkrK4AHpxuAA1V2NOGgA6IA – Iain Shelvington Oct 05 '22 at 21:43

1 Answers1

1

How we would normally do it is this:

type Unificator<T extends Record<string, unknown>> = T[keyof T];

You can find a small explanation here.

To intersect the members of the union together, we have to pull out our TypeScriptnomicon and flip to page 50374908:

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

Then we can intersect the union from earlier:

type Intersector<T extends Record<string, unknown>> = UnionToIntersection<T[keyof T]>;

Also, the generic constraint Record<string, unknown> is not necessary in either of these, so you could omit those if you wish.

kelsny
  • 23,009
  • 3
  • 19
  • 48
  • WOOOOOOOOAH! That's amazing! Thank you! Also, loved "TypeScriptnomicon" ahah. Seems a very good place to learn about the type system (even though I should probably go over the basics in the TypeScript manual, which I have not done for realsies yet). Thanks! – damix911 Oct 05 '22 at 22:25
  • @damix911 This isn't exactly covered in the handbook, but it utilizes some of the concepts (particularly [distributive conditional types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types) and how [functions are contravariant](https://dmitripavlutin.com/typescript-covariance-contravariance/)). – kelsny Oct 05 '22 at 22:28