Reputation: 159
I have a single type generated from a Swagger API definition.
Each API endpoint has it's API response nested in the Endpoints type. Currently I am accessing like this:
type Endpoint1Return = Endpoints['/endpoint1']['get']['responses']['200']['content']['application/json']
type Endpoint2Return = Endpoints['/endpoint2']['get']['responses']['200']['content']['application/json']
I want a utility type to pick the nested application/json
key for each endpoint. Something like:
type Endpoint1Return = PickEndpoint<'/endpoint1'>
This is difficult because TS doesn't know that each key of Endpoints
has the same child keys for get
, responses
, 200
etc.
Can anyone think of a way to express this?
Upvotes: 0
Views: 151
Reputation: 329658
Given your updated question, I'd say this is a bug in TypeScript, as reported in microsoft/TypeScript#27709. It seems that the compiler does not properly evaluate a constraint for nested generic lookup types like Endpoints[K][L]["responses"]
. That sub-property lookup type seems to mess things up, as you noticed:
type PickEndpointWithMethod<K extends keyof Endpoints, L extends keyof Endpoints[K]> =
Endpoints[K][L]['responses']['200']['content']['application/json']; // error!
// Type '"responses"' cannot be used to index type 'Endpoints[K][L]'.
// Type '"200"' cannot be used to index type 'Endpoints[K][L]["responses"]'.
// Type '"content"' cannot be used to index type 'Endpoints[K][L]["responses"]["200"]'.
// Type '"application/json"' cannot be used to index type
// 'Endpoints[K][L]["responses"]["200"]["content"]'.
That bug does not seem to be scheduled for fixing anytime soon.
As a workaround, you could calculate the constraint of an indexed access yourself, and use the Extract
utility type to hint to the compiler that the lookup type does meet that constraint:
type Idx<T, K extends keyof T> = Extract<T[K], T extends any ? T[keyof T] : never>;
Here, we are assuring the compiler that T[K]
will extend T[keyof T]
even if T
is a union of things. (The T extends any ? T[keyof T]: never
construction uses distributive conditional types to do so). Then we can replace, Endpoints[K][L]
with Idx<Endpoints[K], L>
, like so:
type PickEndpointWithMethod<K extends keyof Endpoints, L extends keyof Endpoints[K]> =
Idx<Endpoints[K], L>['responses']['200']['content']['application/json']; // okay
Let's see if it works:
type Endpoint1Return = PickEndpointWithMethod<'/endpoint1', 'get'>
// type Endpoint1Return = "https://example.com/foo"
type Endpoint2Return = PickEndpointWithMethod<'/endpoint2', 'post'>
// type Endpoint2Return = "https://example.com/bar"
Looks good!
Upvotes: 2