I try to implement a function which will return different types following its parameters.
Something like the function below, but more efficient (with generic and without the union type as result)
type ResultType = {
get: GetResult
post: PostResult
...
}
function fetch(operation: keyof ResultType): GetResult | PostResult | ...
So I start with the example provided in: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html.
When I try to implement the function of the example :
interface IdLabel {
id: number /* + d'autres champs */
}
interface NameLabel {
name: string /* + d'autres champs */
}
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
if (typeof idOrName === 'number') {
return { id: idOrName }
} else {
return { name: idOrName }
}
}
I’ve got the following errors that I cannot understand:
Type '{ id: number; }' is not assignable to type 'NameOrId<T>'.
Type '{ name: string; }' is not assignable to type 'NameOrId<T>'.
What did I do wrong?
Bonus question: Is there another way in TS to manage lot of conditional cases (kind of switch) ?
2
Answers
Well, you’re right: discriminated unions don’t help much.
I think I’ve found a solution, but I don’t know if you like that. Feel free to consider or reject.
The idea is based on Indexed Access Types.
Let’s imagine a series of types, as you suggested, even a little complex. Note that I like more
type
thaninterface
, but I think should work the same.At this point, just define our function, along its input and output types. I assigned each type to a specific key, as index.
So far, the only "ugly" part is the discrimination of the input. That has to be at runtime and the
type
argument comes to rescue.The usage is pretty straightforward, although you’ve to specify the
type
as discriminator.It seems that the trick can be done even without the explicit discrimator. However, it depends on the ability to discriminate the cases against the input.
Here is the playground.
This is a solution to the original problem you stated, that of relating the return type to the input type.
You can use an indexed access type to specify the return type in terms of the input type.
Unfortunately, TypeScript does not fully support this yet. There is an open issue at ms/TS#33014 for proper support under the name of "Dependent-Type-Like Functions". A workaround for your simple case is to assert the type of the returned values.
The following code demonstrates a possible approach. I have had to give my own definitions of the missing types.
Playground of the above
If you have many operations, you could use a function map. Defining
ResultType
in terms of that would it all less error prone, as you wouldn’t have to make sure types match despite assertions; instead, the types are basically asserted to be themselves.Here’s a possible implementation of that:
Playground of above