TLadd
TLadd

Reputation: 6884

TypeScript conditional types with classes that are structurally equivalent

I have something like the following:

declare class Base {
  constructor(a: string)
}

declare class A extends Base {
  constructor(a: number)
}

declare class B extends Base {
  constructor(a: object)
}

declare class C extends Base {
  constructor(a: boolean)
}

type ResponseMapper<T extends Base> = T extends A 
  ? number 
  : T extends B 
  ? object 
  : T extends C 
  ? boolean 
  : never;

declare function getValue<T extends Base>(input: T): ResponseMapper<T>

let x: number = getValue(new A(1))
let y: object = getValue(new B({}))
let z: boolean = getValue(new C(true))

TS Playground

This doesn't quite work because A, B, and C are all structurally equivalent. So in ResponseMapper, if I pass an instance of B or C in, it still evaluates to number. So the variable declarations for y and z end up being type errors.

I figured out a workaround is to add a fake property to each class that makes the types no longer be structurally equivalent. So this works:

declare class Base {
  constructor(a: string)
}

declare class A extends Base {
  _name?: "A"
  constructor(a: number)
}

declare class B extends Base {
  _name?: "B"
  constructor(a: object)
}

declare class C extends Base {
  _name?: "C"
  constructor(a: boolean)
}

type ResponseMapper<T extends Base> = T extends A 
  ? number 
  : T extends B 
  ? object 
  : T extends C 
  ? boolean 
  : never;

declare function getValue<T extends Base>(input: T): ResponseMapper<T>

let x: number = getValue(new A(1))
let y: object = getValue(new B({}))
let z: boolean = getValue(new C(true))

TS Playground

Is there a better way to get this to work that doesn't require adding a fake field?

Upvotes: 1

Views: 56

Answers (1)

Wex
Wex

Reputation: 15705

Here, try using this type:

type ResponseMapper<T, U> = T extends new (a: infer R) => U ? R : never;

declare function getValue<T extends new (a: V) => U, U, V = ResponseMapper<T, U>>(input: U): V;

TS Playground

Upvotes: 1

Related Questions