Home > Mobile >  Indexing object with a generic key
Indexing object with a generic key

Time:05-02

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.

Sandbox

  • Related