Search code examples
typescripttypescript-types

Constrain type to keys of array but respect length


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

Solution

  • 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
    

    Playground

    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.

    Playground