简体   繁体   中英

Typescript generics type inferring

I am playing with typescript by implementing a strongly typed rest request mechanism.

Let the code speak:

With this type I want to define the mapping between the routes and the related type of objects:

export interface RoutesMapping {
    api1: {
        users: UserApiModel
        products: ProductApiModel,
    }
    api2: {
        "other-route": OtherModel1,
        "another-one-route": OtherModel2
    }
}

export type ApiScope = keyof RoutesMapping

The following function is the one I am willing to use to make POST requests

export type RestApiPostResponse<T = any> = {
    result: boolean
    data: T
}

export function restPost<S extends keyof RoutesMapping = "api1", T extends keyof RoutesMapping[S] = keyof RoutesMapping[S]>(
  route: T,
  // nervermind this object, is out of scope for the question
  options: ApiRequestOptions<S, any> = {}
): Promise<RestApiPostResponse<RoutesMapping[S][T]>> {
  const url = apiUtils.buildUrl(route as string, options)
  const { payload, headers, isProtected } = options
  return post({
    url,
    isProtected,
    payload,
    headers
  });
}

I expect to call this function in the following way

const data = await restPost("users")

An make typescript infer the return type by inferring it by the scope and the route.

Actually, using it with the default type parameters, it works:

在此处输入图像描述

The problem is when I when I want to call the other api in this way:

const data = await restPost<"api2">("other-route")

Unfortunately, it does not work and it infers all the possible types

在此处输入图像描述

The only way to solve the problem is to explicitly add the second type parameter

在此处输入图像描述

How can I use all of this without needing to add the second type parameter in the second scenario?

Here is a typescript playground

If you infer the api-key type parameter, you can actually construct a solution that does what you want:

type Model<Route> = // Returns the model value for key Route in RoutesMapping
  keyof RoutesMapping extends infer Api
  ? Api extends keyof RoutesMapping 
    ? Route extends keyof RoutesMapping[Api]
      ? RoutesMapping[Api][Route]
      : never
    : never
  : never

type Routes<Api> = Api extends {} ? keyof Api  : never // Helper to distribute keyof over a union of objects
type AllRoutes = Routes<RoutesMapping[keyof RoutesMapping]> // Union of all route keys: 'users' | 'products' | 'other-route' | 'another-one-route'

export function restPost<Route extends AllRoutes>(
  route: Route,
  options?:{url:string,payload:any}
): Promise<RestApiPostResponse<Model<Route>>> {
 ..
}

When applied to a route string, the correct return type for restPost is inferred, without needing to specify any type parameters:

const data = await restPost("users") // data: RestApiPostResponse<UserApiModel>
const data2 = await restPost("other-route") // data2: RestApiPostResponse<OtherModel1>

TypeScript playground

Note that this assumes the route keys are unique, which seems to be the case since the api key is not passed to restPost . I'm also not certain it is wise to introduce all this complexity, but at least it is possible.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM