Home > front end >  Typed Array to literal Type in TypeScript
Typed Array to literal Type in TypeScript

Time:11-05

How can a typed array of strings be used as a union of literal types based on their actual values? On a high level, I have a typed array like ['hello', 'world'] and what I want to infer from that is a new type of 'hello' | 'world'.

const someArray: Readonly<Array<string>> = ['hello', 'world'] as const;
type SomeType = typeof someArray[number]; // string

SomeType will now be inferred to be string instead of the union. How can I infer a union of the literal types?

This question is very similar to TypeScript array to string literal type with the difference, that the array is typed. I cannot remove that. The code below would work, but someArray is actually typed as Array<string>.

const someArray = ['hello', 'world'] as const;
type SomeType = typeof someArray[number]; // 'hello' | 'world'

Is there any way to narrow the inferred type accordingly?

edit: This example is obviously simplified. I use a third-party library that expects an array of objects. These are typed by the third-party and I cannot change that without loosing type support. I realize that it would work without a type, but I cannot realistically remove that.

CodePudding user response:

You can't get TypeScript to infer something that's in direct contradiction to what it's been told explicitly (Readonly<Array<string>>). That type annotation wins the day.

The fix is, as you've indicated, to remove the type annotation. But you've said you can't do that.

Since you can't remove the type annotation — e.g., the array is defined by code you don't control — then you have no choice but to duplicate the list of possible values in the type, which leaves you open to maintenance issues when the original changes and you don't update the duplicate. To mitigate that, you can add a runtime check to handle the case where the original is changed so they no longer match:

const duplicatedArray = ['hello', 'world'] as const;
type SomeType = typeof duplicatedArray[number]; // 'hello' | 'world'
if ( someArray.length !== duplicatedArray.length ||
     someArray.some((e, i) => e !== duplicatedArray[i])) {
    throw new Error(`'someArray' and 'duplicatedArray' no longer match`);
}

(The runtime part of that might be in an automated test rather than the main code.)

Very much a second- or third-best solution, but if you have no choice, it's at least something.

  • Related