typescriptvariadic-tuple-types

How can I get tuple type from object with array of keys


With function which returns values from object with some list of keys from the object like below.

function mapObjToArray(obj, keys) {
    return keys.map(key => obj[key])
}

const result = mapObjToArray({ A: "a", B: "b", C: 'c', X: 'x'}, ['A', 'B'])

I want get exact type of result value, I mean ["a", "b"] in this case, but, with code below, type of result is ("a" | "b" | "c" | "x")[] instead of ["a", "b"].

function mapObjToArray<T>(obj: T, keys: (keyof T)[]) {
    return keys.map(key => obj[key])
}

const result = mapObjToArray({ A: "a", B: "b", C: 'c', X: 'x'} as const, ['A', 'B'])

I may advanced little with Variadic Tuple Type and I got...

function mapObjToArrayWithVariadicTuple<T, X extends [...(keyof T)[]]>(obj: T, keys: X): [...T[X[number]][]] {
    return keys.map(key => obj[key])
}

const resultVariadicTuple = mapObjToArrayWithVariadicTuple({ A: "a", B: "b", C: 'c', X: 'x'} as const, ['A', 'B'])

Getting closer, but still, typeof resultVariadicTuple is not I wanted ("a" | "b")[]

Is there any way to get the result type I want???

TypeScript Playground


Solution

  • One sure thing is that both obj and keys parameters must be const. Otherwise, as types in TypeScript are detached from the runtime, the returned type is undeterminable.

    If this requirement can be assumed, then something like this should be the answer:

    type _Map<
      K extends readonly any[],
      O extends { [key in K[number]]: any }
    > = K extends readonly []
      ? []
      : K extends readonly [infer H, ...infer T]
      ? [O[H], ..._Map<T, O>]
      : never;
    
    function mapObjToArray<
      K extends readonly (string | number | symbol)[],
      O extends { [key in K[number]]: any }
    >(obj: O, keys: K): _Map<K, O> {
      return keys.map((key: K[number]) => obj[key]) as any;
    }
    
    const result = mapObjToArray(
      { A: "a", B: "b", C: "c", X: "x" } as const,
      ["A", "B"] as const
    );