It is possible to achieve with help of function-overloads.
First of all, lets define callbackFn
:
type CallbackFn<T> = T extends string ? "string !" : "not string!";
function callbackFn<T>(entry: T): CallbackFn<T>
function callbackFn<T>(entry: T) {
return typeof entry === 'string' ? "string !" : "not string!"
}
I have overloaded callbackFn
and used CallbackFn
type utility to infer return type.
Now, we need type utility for our mapping. Consider this:
type MapUtility<T extends ReadonlyArray<unknown>> = {
[Prop in keyof T]: CallbackFn<T[Prop]>
}
type Test1 = MapUtility<["foo", 42]> // ["string!", "not string!"]
Since all utils are set we can write our main function. Keep in mind, that it is not necessary to use as const
to infer literal argument. You can use variadic-tuple-types:
function map<
Elem,
Tuple extends Elem[]
>(tuple: [...Tuple]): MapUtility<Tuple>
function map(tuple: unknown[]) {
return tuple.map(callbackFn)
}
// ["string !", "not string!"]
const output = map(["foo", 42])
Whole example:
type CallbackFn<T> = T extends string ? "string !" : "not string!";
function callbackFn<T>(entry: T): CallbackFn<T>
function callbackFn<T>(entry: T) {
return typeof entry === 'string' ? "string !" : "not string!"
}
type MapUtility<T extends ReadonlyArray<unknown>> = {
[Prop in keyof T]: CallbackFn<T[Prop]>
}
type Test1 = MapUtility<["foo", 42]> // ["string!", "not string!"]
function map<
Elem,
Tuple extends Elem[]
>(tuple: [...Tuple]): MapUtility<Tuple>
function map(tuple: unknown[]) {
return tuple.map(callbackFn)
}
// ["string !", "not string!"]
const output = map(["foo", 42])
Playground
Please keep in mind that typescript does not preserve tuple length after map
. See this answer.
This answer is related
P.S. If you are interested in function argument inference in TypeScript you can check my article