How can I infer a class generic from it's own method
class Controller<Body extends Record<string,any>> {
public main(body: Body) {
// This should have auto complete too for the body.a
console.log({ body })
}
public getBody(): Body {
return {a:'b'} //error
}
}
// This should not need any generic parameter
const controller = new Controller()
// This should have auto complete
controller.main({a:'b'})
The error is
Type '{ a: string; }' is not assignable to type 'Body'. '{ a: string; }' is assignable to the constraint of type 'Body', but 'Body' could be instantiated with a different subtype of constraint 'Record<string, any>'.(2322)
I removed some of the code. Originally it uses zod
object for the body. Also this class should be abstract but I removed that too to make it simpler.
Generics in TypeScript can't be used to do what you're trying to do. Generic class type arguments are chosen by the caller of the constructor, not the implementer of the class. If Controller
is generic in Body
, that means calling new Controller()
is when Body
gets specified, not inside getBody()
. Trying to use generics for this purpose is backwards.
Really you do not want Controller
to be generic. If you want to try to compute the return type of getBody()
from the implementation, you could use the indexed access type Controller["getBody"]
to get the type of the method, and then the ReturnType
utility type, like ReturnType<Controller["getBody"]>
.
Bet even this is much more complicated and not a common approach. Generally speaking people just create named types to represent things they want to reuse:
interface Body {
a: string;
}
class Controller {
public main(body: Body) {
console.log({ body })
}
public getBody(): Body {
return { a: 'b' }
}
}
const controller = new Controller()
controller.main({ a: 'b' })
Or, if you really need to compute the type, move that logic out of the class so you don't create a possibly circular dependency:
const body = { a: "b" };
type Body = typeof body;
class Controller {
public main(body: Body) {
console.log({ body })
}
public getBody(): Body {
return body;
}
}
const controller = new Controller()
controller.main({ a: 'b' })
This is the same basic type, but the Body
type is computed.