Reputation: 1198
Is it possible to apply overloads of a TypeScript function based on the class type of the parameter?
I realize that the "classes" will be compiled down to plain functions in the output, but am hoping to get the type checking to work depending on the input type.
The goal is to create a send()
function for server communication that returns a typed response based on the type of request. However, TypeScript appears to be always selecting the first overload regardless of the class type passed in.
interface IRequest {
getBody(): object;
}
class UserRequest implements IRequest {
getBody() {
return { type: 'user', id: 7 };
}
}
class CompanyRequest implements IRequest {
getBody() {
return { type: 'company', id: 42 };
}
}
interface IUserResponse {
userName: string;
}
interface ICompanyResponse {
companyName: string;
}
function send(request: UserRequest): Promise<IUserResponse>;
function send(request: CompanyRequest): Promise<ICompanyResponse>;
async function send(request: IRequest): Promise<any> {
const response = await fetch('https://example.com/service', {
body: request.getBody(),
});
return response.json();
}
async function test() {
// Works
const userResponse = await send(new UserRequest());
userResponse.userName;
// Error: [ts] Property 'companyName' does not exist on type 'IUserResponse'. [2339]
const companyResponse = await send(new CompanyRequest());
companyResponse.companyName;
}
Swapping the order of the declared overloads reverses the problem (the return type will always be CompanyRequest
instead of UserRequest
).
Upvotes: 0
Views: 559
Reputation: 328057
Your problem seems to be that the compiler thought that both UserRequest
and CompanyRequest
are structurally compatible, meaning they are essentially the same type. So the first overload is always chosen, and bad things happen. The easiest way to avoid this is to add a properties to at least one of the types so that the compiler recognizes they are distinct types. Here's one possible way:
class UserRequest implements IRequest {
getBody() {
return { type: 'user', id: 7 };
}
readonly className = "UserRequest"
}
class CompanyRequest implements IRequest {
getBody() {
return { type: 'company', id: 42 };
}
readonly className = "CompanyRequest"
}
In that case the readonly
property className
gets some literal string values that differ. Hope that helps. Good luck!
Upvotes: 2