Reputation: 203
I want to create type
in TypeScript that takes a type parameter T
and tuple/ReadonlyArray of keyof T
and returns a ReadonlyArray of the keys indexed into T
.
type TupleIndexed<T, K extends ReadonlyArray<keyof T>> = {
[C in keyof K]: T[C];
};
But I get Type 'C' cannot be used to index type 'T'
.
One workaround I found is
type TupleIndexed<T, K extends ReadonlyArray<keyof any>> = {
[C in keyof K]: K[C] extends keyof T ? T[K[C]] : never;
};
While it gets the job done, I don't understand why the conditional is necessary for the compiler.
This way I'm able to write typed functions that preserve the positional type information like the following
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, even in this solution, not casting the array 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'
]); // Not explicitly stating as const still compiles
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
Which loses all the positional type safety. Is there a way to automatically cast the array as const
or otherwise have the compiler throw an error when it isn't casted as const
?
Upvotes: 2
Views: 98
Reputation: 153030
Is there a way to automatically cast the array as
const
or otherwise have the compiler throw an error when it isn't casted asconst
?
As far as I know, there is not.
However, using a rest parameter instead of an array will result in TypeScript treating it as a tuple instead of an array. Of course this also means have to call it slightly differently:
type TupleIndexed<T, K extends ReadonlyArray<keyof any>> = {
[C in keyof K]: K[C] extends keyof T ? T[K[C]] : never;
};
function pluck<T, K extends ReadonlyArray<keyof T>>(obj: T, ...keys: K): TupleIndexed<T, K> {
// ^^^
return keys.map(key => obj[key]) as any;
}
const vals = pluck({name: 'John', age: 25, adult: true}, 'name', 'age', 'adult');
const name = vals[0]; // string
const age = vals[1]; // number
const adult = vals[2]; // boolean
const doesNotExist = vals[3]; // error
Upvotes: 1