First time I've run into this issue so apologies if the title doesn't make sense, couldn't figure out a good way to word it.
I'm working with an API that returns a response like this:
{
PaginatedResults: {
TotalResults: 100,
Offset: 0,
Limit: 0,
PageSize: 100,
},
Error: {
Message: '',
Code: '',
},
Customers: [{...}]
}
Where Customers
can be replaced with any type the API may return. So it could also look something like this:
{
PaginatedResults: {
TotalResults: 100,
Offset: 0,
Limit: 0,
PageSize: 100,
},
Error: {
Message: '',
Code: '',
},
Sales: [{...}]
}
I currently express the type like this:
type PaginatedResponse<TResponse extends object> =
TResponse &
Readonly<{
PaginatedResponse: {
TotalResults: number;
Offset: number;
Limit: number;
PageSize: number;
};
Error?: {
Message: string;
Code: string;
};
}>;
This has been working fine but I recently ran into a issue when I was trying to create a generic helper function to auto paginate an endpoint.
To keep track of all the results I need to do something like this:
const allResults = [...initialResults];
const nextResp = await apiCall();
allResults.push(...nextResp['Customers'])
But the problem is the key 'Customers'
can be any of the 50 types the API may return. So I need a generic way to index into the response. I've tried a bunch of different things with no success so I'm looking to get some guidance on the best way to approach something like this.
CodePudding user response:
TypeScript alone will not be too helpful here. In ...nextResponse['Customers']
you are expecting TypeScript to fill in the correct key. But what the correct key is will only be known either ar runtime or by a manually supplied generic type.
But maybe this will help you anyway:
// I added some generic types here which will help later
type PaginatedResponse<TResponse extends {[key in TKey]: any}, TKey extends keyof TResponse = keyof TResponse> =
Readonly<{
PaginatedResponse: {
TotalResults: number,
Offset: number,
Limit: number,
PageSize: number,
};
Error?: {
Message: string;
Code: string;
};
}> & TResponse
Define possible Response
types:
type Response1 = PaginatedResponse<{Customers: any}>
type Response2 = PaginatedResponse<{Sales: any}>
type AllResponses = Response1 | Response2
Now let's define a function which gets a PaginatedResponse
and pushes the TResponse
in an array:
type GetResponseKey<T> = T extends PaginatedResponse<infer TRes, infer TKey> ? TKey : never
const arr: any[] = []
function pushToResponseArray<
T extends AllResponses,
K extends GetResponseKey<T>
>(response: T, key: K){
arr.push(response[key])
}
const res: Response1 = {} as any
pushToResponseArray(res, "Customers") // We have to manually specify the key here
// but typescript will check if key is correct
This is not a perfect solution and I think you always have to either manually set the key
or determine it with some JavaScript logic.
To sum it up:
Somewhere in your application you need some JavaScript logic to determine the type of the PaginatedResponse
. Map the type to the corresponding key afterwards to access the TResponse
.