cantfigurethisout
cantfigurethisout

Reputation: 13

Typescript Constructor Parameter Factory Typing

I have classes whose constructors require objects which form a tagged union:

interface Info {
  x: string;
}

interface AInfo extends Info {
  x: 'a';
}

class A {
  constructor(info: AInfo) {}
}

interface BInfo extends Info {
  x: 'b';
}

class B {
  constructor(info: BInfo) {}
}

type AllInfos = AInfo|BInfo;

I've omitted class internals and constructor initialization. How can I type this factory function to correlate the type of object created based on the info passed in?

const factory = (info: AllInfos) => {
  switch (info.x) {
    case 'a': return new A(info);
    case 'b': return new B(info);
    // ...
  }
};

const obj = factory(myInfo); // type of obj is A|B, I'd like it to vary by myInfo

Upvotes: 1

Views: 61

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250036

The simplest way to do it is by using overloads:

function factory(info: AInfo): A
function factory(info: BInfo): B
function factory(info: AllInfos) {
  switch (info.x) {
    case 'a': return new A(info);
    case 'b': return new B(info);
    // ...
  }
};

const objA = factory({ x: 'a' }); // A
const objB = factory({ x: 'b' }); // B

Playground Link

If there are a lot of options in the union, you can get creative and create a distributive conditional type to create a signature for each member in the union, and then make it into an overloaded signature using UnionToIntersection (see here):

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

type Factory<T> = T extends new (p: infer P) => infer R ? (a: P) => R : never
type FactoryParameters<T> = T extends new (p: infer P) => infer R  ? P : never
type AllClasses = typeof A | typeof B

const factory = ((info: FactoryParameters<AllClasses>) => {
  switch (info.x) {
    case 'a': return new A(info);
    case 'b': return new B(info);
  }
}) as UnionToIntersection<Factory<AllClasses>>;

const objA = factory({ x: 'a' }); // A
const objB = factory({ x: 'b' }); // B

Playground Link

Upvotes: 1

Related Questions