Reputation: 75
I have the following piece of code and I'd like to infer the type of an object's property. As you can see, the inferred type of v
in my example is {}
.
How can I write myValue
so that C
is inferred at compile time by Typescript?
const myValue = <T1, T2>(prop: string, obj: T1): T2 => {
return obj[prop]
}
interface C {
readonly c: string
}
interface TestInt {
readonly a: string
readonly b: number
readonly c: C
}
const test: TestInt = {
a: 'a',
b: 1,
c: {
c: 'c',
},
}
const v = myValue('c', test) // const v: {}
Upvotes: 1
Views: 881
Reputation: 327944
Type parameter inference works best when you have an actual value of the type you're trying to infer. Having T2
be the type of the return value of the function is likely to be a problem. Unless you call myValue()
in a context that expects the return value to be of type C
, there is little hope that the compiler will infer C
for it... inference of a type parameter from a return value is called contextual typing, and unfortunately in const v = myValue(...)
, there is no contextual type for v
. It can be anything.
So what types do you actually have values of? Since you call myValue()
with prop
and obj
parameters, the first thing to try is to give obj
a generic type like T
and to give prop
another generic type like K
.... and infer T
and K
from obj
and prop
respectively.
Note that you intend that prop
should be one of the keys of obj
, so you have a natural constraint on the type of K
, namely that it should extend keyof T
.
Finally, the return type of your function can be derived from the other types. It is meant to be the type of the property of T
whose key is K
. That can be represented as T[K]
(a lookup type).
Here's the final version of the function:
const myValue = <T, K extends keyof T>(prop: K, obj: T): T[K] => {
return obj[prop];
}
Note the similarity between that function and the getProperty()
function mentioned in the documentation for keyof
and lookup types. The only difference is the order of parameters (well, and the fact that the return type is inferred instead of annotated.... and the function name, I guess).
When we use that defintion of myValue()
, we get:
interface C {
readonly c: string
}
interface TestInt {
readonly a: string
readonly b: number
readonly c: C
}
const test: TestInt = {
a: 'a',
b: 1,
c: {
c: 'c',
},
}
const v = myValue('c', test) // const v: C
as you wanted.
Hope that helps; good luck!
Upvotes: 4