Home > OS >  Intersecting an arbitrary number of generic types
Intersecting an arbitrary number of generic types

Time:10-06

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.

CodePudding user response:

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.

  • Related