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

1k Views Asked by At

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

2

There are 2 best solutions below

0
Y. K On BEST ANSWER

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
);
0
ImFonky On

I hope it helps

//////////////////////////////////////////////////////////////
// This is where the magic happens
type Tobj<O> = {
  [key in keyof O]: [key, O[key]];
};
type ObjectToTuple<O> = Tobj<O>[keyof Tobj<O>];
//////////////////////////////////////////////////////////////


const test = {
  a: "a",
  b: "b",
  c: 1
} as const;

type T = typeof test;

type TupleT = ObjectToTuple<T>;

(Object.entries(test) as TupleT[]).map((entry) => {
  switch(entry[0]){
    case "a":{
      return entry[1] === "a"; // TRUE!
    }
    case "b":{
      return entry[1] === "b"; // TRUE!
    }
    case "c":{
      return entry[1] === 1; // TRUE!
    }
  }
});

Note that you still have to cast the array because typescript cannot guarantee you that the object doesn't have more attributes than the one listed in the type