Looking to define a TypeScript type
that accepts a type parameter T
along with a tuple or ReadonlyArray of keyof T
, and returns a ReadonlyArray containing the keys indexed into T
.
type TupleIndexed<T, K extends ReadonlyArray<keyof T>> = {
[C in keyof K]: T[C];
};
Encountering the error message
Type 'C' cannot be used to index type 'T'
when trying this.
One workaround is as follows:
type TupleIndexed<T, K extends ReadonlyArray<keyof any>> = {
[C in keyof K]: K[C] extends keyof T ? T[K[C]] : never;
};
Although this resolves the issue, it's not clear why the conditional statement is needed for the compiler to understand.
This approach allows for creating typed functions that maintain positional type information, illustrated below:
function pluck<T, K extends ReadonlyArray<keyof T>>(obj: T, keys: K): TupleIndexed<T, K> {
return keys.map(key => obj[key]);
}
const vals = pluck({name: 'John', age: 25, adult: true}, [
'name',
'age',
'adult'
] as const);
const name = vals[0]; // string
const age = vals[1]; // number
const adult = vals[2]; // boolean
const doesNotExist = vals[3]; // ERROR
However, omitting the array casting as const
still compiles:
function pluck<T, K extends ReadonlyArray<keyof T>>(obj: T, keys: K): TupleIndexed<T, K> {
return keys.map(key => obj[key]);
}
const vals = pluck({name: 'John', age: 25, adult: true}, [
'name',
'age',
'adult'
]); // Compiles without explicit cast as const
const name = vals[0]; // string | number | boolean
const age = vals[1]; // string | number | boolean
const adult = vals[2]; // string | number | boolean
const doesNotExist = vals[3]; // string | number | boolean
This leads to loss of positional type safety. Is there a way to automatically enforce the array to be casted as const
, or trigger an error when it's not explicitly done?