0

I have the follolwing working code:

interface Box {
    length: number;
    width: number;
    height: number;
}

const box: Box = {
    length: 2,
    width: 1,
    height: 3
};

type PickWithArray<T, A extends readonly (keyof T)[]> = Pick<T, A[number]>;

const keysArray = ["length", "width"] as const;
type Extracted = PickWithArray<Box, typeof keys2>;

This works great, but when I create keysArray, I'd like to constrain the array's values to keys of the type so I can get autocomplete in my IDE. I've attempted the following:

type PickWithArrayKeys<T> = Array<keyof T>;
const pickedKeys: PickWithArrayKeys<Box> = ["length", "width"];
const keysArray = [...pickedKeys] as const; // Doesn't work, original type is persisted
type Extracted = PickWithArray<Box, typeof keysArray>;

This doesn't work, as typeof keysArray is actually ("length" | "width" | "height")[] so Extracted ends up being:

type Extracted = {
    length: number;
    width: number;
    height: number; <-- want this to be excluded
}
Mister Epic
  • 16,295
  • 13
  • 76
  • 147

1 Answers1

0

I would introduce a helper function to constrain the values in the array to elements of the keys of Box without just producing Array<keyof Box>:

const asKeysArray = <T>() => <K extends keyof T>(...k: K[]) => k;
const asBoxKeysArray = asKeysArray<Box>();

The first asKeysArray() function takes a type T and produces a new function which only accepts keys of the T type. If we give it Box we get asBoxKeysArray(). Then this function can be called like this:

const keysArray = asBoxKeysArray("length", "width");
// const keysArray: ("length" | "width")[]

You can see that keysArray is of type Array<"length" | "width">, which is specific enough to make Extracted what you want:

type Extracted = PickWithArray<Box, typeof keysArray>;
/*
type Extracted = {
    length: number;
    width: number;
}
*/

Aside: you might wonder why there isn't a single function function asKeysArray<T, K extends keyof T>(...k: K[]): K[];. That is, why did I make a generic function take T and return a new generic function taking K? The answer is that there's currently no way to do partial type parameter inference in TypeScript (see related question), so if we want to specify T but have K inferred, we have to do this function-returning-function thing or some other workaround.

Anyway, that should work for you. Hope that helps; good luck!

Link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
  • Interesting, I've seen the function returning a function pattern before but never really understood it until now. – Mister Epic Oct 09 '19 at 17:57