I have an array and I want to get a type which constrains its possible values to just valid keys. I thought that keyof typeof array
would solve this, but it accepts all numbers.
const arr = ["a", "b", "c"] as const
type keyOfArr = keyof typeof arr // Does not work work. Accepts all numbers
let x: keyOfArr = 1 ✔️ Should work
let x: keyOfArr = 2 ✔️ Should work
let y: keyOfArr = -1 ❌ Should not work
let z: keyOfArr = 999 ❌ Should not work
CodePudding user response:
You need recursively iterate through the List and incrementaly add length
to accumulator Result
:
const arr = ["a", "b", "c"] as const
type List = (typeof arr)
type AllowedIndexes<T extends readonly any[], Result extends any[] = []> =
T extends readonly [infer _, ...infer Rest]
? AllowedIndexes<Rest, [...Result, Result['length']]>
: Result[number]
type Result = AllowedIndexes<List>
let x: Result = 1 // ok
let xx: Result = 2 // ok
let y: Result = -1 // error
let z: Result = 999 // error
Since TypeScript 4.8, it is doable without recursion:
const arr = ["a", "b", "c"] as const
type List = (typeof arr)
type ParseInt<T> = T extends `${infer Digit extends number}` ? Digit : never
type AllowedIndexes<T> =
{
[Prop in keyof Omit<T, number>]:
(Prop extends `${number}`
? ParseInt<Prop> : never)
}[keyof Omit<T, number>]
type Result = AllowedIndexes<List>
let x: Result = 1 // ok
let xx: Result = 2 // ok
let y: Result = -1 // error
let z: Result = 999 // error
Thanks to this PR, it is possible to infer numeric value from template string. It works only in TS nightly.