1

Let's assume that I have a Typescript function like this:

const myFunction = <U, V extends keyof U> (object: U, key: V) => {
    // ...
}

How can I tell Typescript to check that object[key] is of certain type?

For example, let's say that I want object[key] be of type 'string'.

So, when I call myFunction it should work like this:

myFunction({keyA: "hello", keyB: 123}, "keyA") // OK

myFunction({keyA: "hello", keyB: 123}, "keyB") // error: `keyB` must be of type string but is number
user246185
  • 136
  • 2
  • 6

1 Answers1

2

Based on the answer at In TypeScript, how to get the keys of an object type whose values are of a given type? I put together this worked example answering your question specifically ( see playground ) but the hard part is really from the other answer.

type KeysMatching<T,V> = keyof { [ P in keyof T as T[P] extends V ? P : never ] : P };

function myFunction<T>(object: T, key: KeysMatching<T,string> ){
    // ...
}

myFunction({keyA: "hello", keyB: 123}, "keyA") // OK

myFunction({keyA: "hello", keyB: 123}, "keyB") // error: `keyB` must be of type string but is number

I quite like the form of extracting a PickValues<T,V> type which filters the properties of T to only those with values in V. Then simply use keyof on that...

type PickValues<T,V> = { [ P in keyof T as T[P] extends V ? P : never ] : T[P] }
type Picked = PickValues<{keyA: "hello", keyB: 123},string>;
// type Picked = {
//     keyA: "hello";
// }

//Your case
function myFunction<T>(object: T, key: keyof PickValues<T,string> ){
    // ...
}
myFunction({keyA: "hello", keyB: 123}, "keyA") // OK
myFunction({keyA: "hello", keyB: 123}, "keyB") // error: `keyB` must be of type string but is number

Incidentally, this strategy can then be generalised to do the opposite and create an OmitValues<T,V> type...

type OmitValues<T,V> = { [ P in keyof T as T[P] extends V ? never : P ] : T[P] }
type Omitted = OmitValues<{keyA: "hello", keyB: 123},string>;
// type Omitted = {
//     keyB: 123;
// }

Finally, it can also be repurposed to extract only properties which are of an Optional nature...

type OptionalValues<T> = { [ P in keyof T as undefined extends T[P] ? P : never ] : T[P] }
type Optional = OptionalValues<{keyA?: "hello", keyB: 123}>;
// type Optional = {
//     keyA?: "hello" | undefined;
// }
cefn
  • 2,895
  • 19
  • 28
  • 1
    This was a really inspiring and challenging question (though it looks disarmingly easy). It taught me new stuff about Typescript. From there, I've been trying to perfect the answer above. I found constructing utility types PickValues and OmitValues to be a good way to reason about the problem and define the key unions. I also discovered I could extract OptionalValues. The playground is at https://tsplay.dev/m3AV2w – cefn Apr 08 '21 at 18:51
  • 1
    It doesn't really change the answer, but writing it in a different way helped me follow the solution a bit better. – cefn Apr 08 '21 at 18:52