I_Adze
I_Adze

Reputation: 89

Can you declare a type whose index signature is based on a variable?

I'm making an API call that is of the format url/ids=[id1,id2,id3] and returns an object of the form { id1: {...}, id2: {...}, id3: {...} }

I can type this as interface ApiResponse { [key: number]: ResponseShape }, but I actually have more type information than that, as that type loses the relationship between the inputted ids and the response object's keys.

Is there a way to use the variable ids in the type? I know TS is compile time, but I was hoping I for the following scenario:

function apiCall(ids: number[]) {
  const someRandomNumber = 10;
  fetch('url').then(
    res => {
      const data = res.json() as ApiResponse<ids>;
      // fine to access by the id
      const works = data[ids[0]];
      
      // undefined, because 5 isn't one of the ids we requested so won't be in the reponse type.
      const wontWork = data[someRandomNumber]
    }
  );
}

I'm guessing this doesn't exist since it's pretty niche and right on the border of what should happen at compile time, but I'm hoping there's a way to do type ApiResponse<T extends number[]> = { [id in T[number]]: ResponseShape } and pass the ids parameter in as a type

Upvotes: 0

Views: 57

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370689

If you make apiCall generic and then use T[number] to get a union of all the passed IDs, you can then construct a Record where the keys are those numbers.

type ApiResponse<T extends number> = Record<T, 'foo'>;
function apiCall<T extends readonly number[]>(ids: T) {
  return fetch('url')
    .then(res => res.json()) as Promise<ApiResponse<T[number]>>;
}
apiCall([1, 2] as const)
  .then((result) => {
    console.log(result[1]); // Allowed
    console.log(result[3]); // Not allowed
  });

You'll have to be sure to pass a tuple (like [1, 2] as const) and not a number[] for the type of the individual IDs to be preserved across the script.

Upvotes: 2

Related Questions