damix911
damix911

Reputation: 4443

Intersecting an arbitrary number of generic types

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:

But the most important question for me is another one:

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.

Upvotes: 1

Views: 83

Answers (1)

tenshi
tenshi

Reputation: 26342

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.

Upvotes: 1

Related Questions