maxpower90
maxpower90

Reputation: 75

How to infer the type of an object's property

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

Answers (1)

jcalz
jcalz

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

Related Questions