In typescipt, I need a way for a function to, given an argument of a particular type, return an object of a type that is related to the type of the argument.
See below for an example. I need a way to make 'const response = ...' more narrowly typed than it is.
The example below is for having requests of particular types linked to responses that are relevant only to the requests given. For example, given a request that finds information about a user, we want to have a the response that contains their name and age. But when given a request that finds information about a car, we want to have a response with information about the make and the miles of the car. We will only ever want to use the 'user' response for the 'user' request and similar for 'car'.
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) // the type here is 'RequestBase | undefined'. Is there any way to automatically narrow it to be FindCarResponse?
function performRequest(req :RequestBase) : RequestBase | undefined {
if (req instanceof FindUserRequest) {
return new FindUserResponse("foo", 23) // hard coded example for convenience
} else if (req instanceof FindCarRequest) {
return new FindCarResponse("toyota", 10000)
}
}
UPDATE : Solution 1 Inspired by Variable return types based on string literal type argument
One approach is to overload the 'performRequest' signature like so:
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) // hard coded example for convenience
} else if (req instanceof FindCarRequest) {
return new FindCarResponse("toyota", 10000)
}
}
However, I would really like for the library that maintains the request and response types to not have to alter the signature of the function in the application that is using the request and response types (performRequest). So I'd still like to hear other solutions.
UPDATE Solution 2 Thanks for Gerrit Birkeland from TS Gitter channel for this:
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 type of response here is ResponseBase, not sure why it's not narrowed
function performRequest< T extends RequestBase>(req :T) :T["_responseType"] {
if (req instanceof FindUserRequest) {
return new FindUserResponse("foo", 23) // hard coded example for convenience
} else if (req instanceof FindCarRequest) {
return new FindCarResponse("toyota", 10000)
} else {
return new ResponseBase()
}
}
Returning the type value from a function is pretty simple. All you need to do is add a : between the closing parenthesis of the signature method ,and the opening curly bracket. After the colon, write the data type the function will return. This may either be a string, number, boolean, void, or and many more.
unknown is the type-safe counterpart of any . Anything is assignable to unknown , but unknown isn't assignable to anything but itself and any without a type assertion or a control flow based narrowing. Likewise, no operations are permitted on an unknown without first asserting or narrowing to a more specific type.
TypeScript Union Type Narrowing To narrow a variable to a specific type, implement a type guard. Use the typeof operator with the variable name and compare it with the type you expect for the variable.
We can return any type of value from the function or nothing at all from the function in TypeScript. Some of the return types is a string, number, object or any, etc. If we do not return the expected value from the function, then we will have an error and exception.
You can (mostly) achieve the desired effect by adding a property to the RequestBase
class. This property doesn't need to be used for anything, but it does need to exist.
(Copied from my gitter message)
class RequestBase {
_responseType: ResponseBase
}
class ResponseBase {}
class FindUserRequest implements RequestBase {
_responseType: FindUserResponse
user_id: string
constructor(user_id: string) {
this.user_id = user_id
}
}
class FindUserResponse {
name: string
age: number
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const request = new FindUserRequest("foo")
const response = performRequest(request) // FindUserResponse | undefined
function performRequest<T extends RequestBase>(req: T): T["_responseType"] | undefined {
if (req instanceof FindUserRequest) {
return new FindUserResponse("foo", 23)
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With