When working with typescript, I am seeking a solution for a function that can take an argument of a specific type and return an object related to that type.
For instance, in the code snippet below, I am trying to narrow down the type of 'const response =' to be more specific than it currently is.
The scenario presented involves associating requests of certain types with responses that are only relevant to those particular requests. For example, when a request pertains to a user, the response should include their name and age. Conversely, if the request relates to a car, the response should contain details about the make and mileage of the vehicle. Each response should correspond exclusively to its associated request, such as 'user' for a user-related request and 'car' for a car-related request.
class RequestBase {
}
class ResponseBase {
}
interface IFindUserReq {
user_id :string
}
class FindUserRequest implements IFindUserReq {
user_id :string
constructor(user_id) {
this.user_id = user_id
}
}
interface IFindUserRes {
name :string
age :number
}
class FindUserResponse implements IFindUserRes {
name :string
age :number
constructor(name, age) {
this.name = name;
this.age = age;
}
}
interface IFindCarReq {
car_id :number
}
class FindCarRequest implements IFindCarReq {
car_id :number
constructor(car_id) {
this.car_id = car_id
}
}
interface IFindCarRes {
make :string
miles :number
}
class FindCarResponse implements IFindCarRes {
make :string
miles :number
constructor(make, miles) {
this.make = make;
this.miles = miles;
}
}
const request = new FindUserRequest("foo")
const response = performRequest(request) // Currently typed as 'RequestBase | undefined'. Is there a way to automatically narrow it down to FindCarResponse?
function performRequest(req :RequestBase) : RequestBase | undefined {
if (req instanceof FindUserRequest) {
return new FindUserResponse("foo", 23)
} else if (req instanceof FindCarRequest) {
return new FindCarResponse("toyota", 10000)
}
}
UPDATE: Proposed Solution 1 Building upon Variable return types based on string literal type argument
One approach involves overloading the signature of 'performRequest' as shown below:
function performRequest(req :FindCarRequest) : FindCarResponse
function performRequest(req :FindUserRequest) : FindUserResponse
function performRequest(req :RequestBase) : ResponseBase | undefined {
if (req instanceof FindUserRequest) {
return new FindUserResponse("foo", 23)
} else if (req instanceof FindCarRequest) {
return new FindCarResponse("toyota", 10000)
}
}
However, ideally, I would prefer not to modify the function signature of 'performRequest' in the application utilizing the request and response types from the library. Further suggestions are welcomed.
UPDATE: Alternative Solution 2 Credit to Gerrit Birkeland from TS Gitter channel for this innovative idea:
class RequestBase {
_responseType : ResponseBase
}
class ResponseBase {
}
interface IFindUserReq {
user_id :string
}
class FindUserRequest extends RequestBase implements IFindUserReq {
_responseType :FindUserResponse
user_id :string
constructor(user_id) {
super()
this.user_id = user_id
}
}
interface IFindUserRes {
name :string
age :number
}
class FindUserResponse extends ResponseBase implements IFindUserRes {
name :string
age :number
constructor(name, age) {
super()
this.name = name;
this.age = age;
}
}
interface IFindCarReq {
car_id :number
}
class FindCarRequest extends RequestBase implements IFindCarReq {
_responseType :FindCarResponse
car_id :number
constructor(car_id) {
super()
this.car_id = car_id
}
}
interface IFindCarRes {
make :string
miles :number
}
class FindCarResponse extends ResponseBase implements IFindCarRes {
make :string
miles :number
constructor(make, miles) {
super()
this.make = make;
this.miles = miles;
}
}
const request = new FindUserRequest("foo")
const response = performRequest<FindUserRequest>(request) // The response type here is ResponseBase, an unexpected behavior
function performRequest< T extends RequestBase>(req :T) :T["_responseType"] {
if (req instanceof FindUserRequest) {
return new FindUserResponse("foo", 23)
} else if (req instanceof FindCarRequest) {
return new FindCarResponse("toyota", 10000)
} else {
return new ResponseBase()
}
}