4

I'm trying to merge all values of an interface, for example:

interface IA {
    a: string;
}
interface IB {
    b: string;
}
interface IC {
    c: string;
}
//...

interface IMap {
    "a": IA;
    "b": IB;
    "c": IC;
    //...
    "z": IZ;
}

Is there a way to create a type like:

type IAll = IMap["a"] & IMap["b"] & IMap["c"] & ... & IMap["z"];

I'm trying using this both:

type valueMerge<T> = T[keyof T];
type valueMerge2<T extends keyof IMap = keyof IMap> = IMap[T];

// using
let t: valueof<IMap> = null as any;
let t2: valueof2 = null as any;

But for each one, the type is IA | IB | IC | ... | IZ and not IA & IB & IC & ... & IZ.

jtwalters
  • 1,024
  • 7
  • 25

1 Answers1

4

This is simlar to a question about how to programmatically turn a union into an intersection, and it has a similar solution.

Normally when you do a lookup of all the properties of an object type, you get a union of all the properties, as you saw. The problem is that keyof T is a union of the property keys, and looking up a union of keys gives you a union of properties, because property lookup is covariant in the property key type... but what you want is an operation contravariant in the property key type. (You can read more about covariance/contravariance here, but basically covariance implies that a union input becomes a union output, while contravariance implies a union input becomes an intersection output).

Luckily function arguments are contravariant, so if we can map your interface to a new one where the old property types are function arguments, we can get to a place where a union of these function types can result in the intersection of their arguments. And type inference in conditional types using the infer keyword can do this for us (see the line that says "if any candidates were inferred from contra-variant positions, the type inferred is an intersection of those candidates").

Here's the code:

type IntersectProperties<T> = { [K in keyof T]: (x: T[K]) => void } extends {
  [K in keyof T]: (k: infer I) => void
}
  ? I
  : never;

The properties of T are mapped to function arguments, and then we infer a single type I corresponding to all the function arguments, which will be an intersection.

Let's see if it works:

type IAll = IntersectProperties<IMap>;
// type IAll = IA & IB & IC & ... & IZ

Looks good. Hope that helps; good luck!

Link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360