Reputation: 828
I have the following code:
type AlphaNumeric = string | number | null | boolean | undefined;
type AlphaNumericKeys<T> = {
[key in keyof T]: key extends string ? (T[key] extends AlphaNumeric ? key : never) : never;
}[keyof T];
This works perfectly fine, of a generic object T it returns all the keys of T whose associated value is what I call AlphaNumeric (Im using this for sorting an array based on some keys).
For example, the Alpha numeric keys of a person are as follows:
type Person = {
name: string;
age: number;
friends: Person[];
doSomething: Function;
}
type PersonAlphaNumericKeys = AlphaNumericKeys<Person> // "name" | "age"
This works perfect right now, the only problem is when I use those keys on T.
type AlphaNumericValuesOfPerson = Person[AlphaNumericKeys<Person>] // string | number
This kind of makes sense at a glance, but when I use a generic it breaks.
type SomeValues<T> = T[AlphaNumericKeys<T>] // T[AlphaNumericKeys<T>], not AlphaNumeric like I would expect.
How can I get T[AlphaNumericKeys] to be the same type as (or at least assignable to) AlphaNumeric? If I have a function that takes AlphaNumeric I want to be able to pass T[AlphaNumericKeys] to it.
Upvotes: 1
Views: 34
Reputation: 327859
Clearly SomeValues<T>
can be narrower than AlphaNumeric
, so you wouldn't want it to evaluate to AlphaNumeric
directly:
type S = SomeValues<{ a: 1, b: "foo", c: false, d: Date }>;
// type S = false | 1 | "foo" // a proper subtype of AlphaNumeric
But you are not happy that the generic SomeValues<T>
is not seen as extending AlphaNumeric
when T
has not yet been specified:
function oops<T>(x: SomeValues<T>) {
const y: AlphaNumeric = x; // error!
// Type 'SomeValues<T>' is not assignable to type 'AlphaNumeric'.
}
This is just a design limitation of TypeScript. See microsoft/TypeScript#30728 for details. The core of SomeValues
is the conditional type of the form (T[K] extends AlphaNumeric ? ...)
. When the compiler sees that a conditional type depends on an as-yet-unspecified type parameter like T
is, it often just defers evaluating that type... and when this happens the compiler cannot really verify that anything specific is assignable to it. It just cannot perform the higher-order reasoning necessary to make the connection between SomeValues<T>
as AlphaNumeric
.
In your case, if you'd like to work around it and tell the compiler that SomeValues<T>
will always be assignable to AlphaNumeric
, you could do so by modifying its definition:
type SomeValues<T> = Extract<T[AlphaNumericKeys<T>], AlphaNumeric>
This is using the Extract
utility type to filter T[AlphaNumericKeys<T>]
to only include those union members assignable to AlphaNumeric
. This will be a no-op for any specific value of T
:
type S = SomeValues<{ a: 1, b: "foo", c: false, d: Date }>;
// type S = false | 1 | "foo" // still the same
but the compiler will accept that Extract<X, Y>
is assignable to Y
no matter what X
is, thus solving the problem with assignability:
function okay<T>(x: SomeValues<T>) {
const y: AlphaNumeric = x; // okay
}
Upvotes: 1