Home > Back-end >  How to index an Object by string without widening Object's type?
How to index an Object by string without widening Object's type?

Time:05-05

Consider the following code. The function getStatusDescription return a description for a given HTTP code if it's found, otherwise return the status code itself.

const StatusDescription = {
  200: 'ok',
  400: 'bad request',
  500: 'internal server error',
}

const getStatusDescription(status: string) = {
  return StatusDescription[status] || status
}

Return line gives an error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ 200: string; 400: string; 500: string; }'.

No index signature with a parameter of type 'string' was found on type '{ 200: string; 400: string; 500: string; }'. (7053)

A possible fix is to set StatusDescription: Record<string, string> = …, but this would wide the Object's key type to all string possibles, which is undesired.

I tried to use return status in StatusDescription ? StatusDescription[status] : status, but TypeScript gives the same error. So I have to manually convert types:

return (
    status in StatusDescription
        ? StatusDescription[status as unknown as keyof typeof StatusDescription]
        : status
)

Which is very verbose. Or, to make it shorter, and maybe more wrong:

return (
    StatusDescription[status as unknown as keyof typeof StatusDescription]
        || status
)

It doesn't give any error, but it seems wrong since we don't really know if status is a keyof StatusDescription at the point the conversion is made.

Is there a non-verbose alternative (comparable to obj[key] || key) without loosing types?

CodePudding user response:

You might want a map which could help with all those verbose casts:

// type is now Map<string, string>
const StatusDescription = new Map(Object.entries({
  200: 'ok',
  400: 'bad request',
  500: 'internal server error',
}));

const getStatusDescription(status: string) = {
  // simple get
  return StatusDescription.get(status) ?? status;
}

// for normal access you can assert that it exists with '!'
StatusDescription.get(500)!; 

?? is nullish coalescing which you can find more about here.

  • Related