bmdelacruz
bmdelacruz

Reputation: 2956

Getting type of a property of a TypeScript class using keyof operator

As stated in the documentation of TypeScript about the keyof operator, one can get a property of an object instance using the function below.

function getProperty<T, K extends keyof T>(o: T, name: K) {
    return o[name];
}

Of course, one can get the type of the property by replacing return o[name] into return typeof o[name]. Is there a way to retrieve the type of the property without passing any object instance?

function getPropertyType<T>(name: keyof T) {
    // something like T[name]?
}

Upvotes: 128

Views: 198577

Answers (5)

GlabbichRulz
GlabbichRulz

Reputation: 1014

Another solution that is especially useful for getting the types of multiple properties is using Pick:

type A = Pick<T, 'prop1' | 'prop2'>

The above is equivalent to:

type A = {
  prop1: T['prop1'], 
  prop2: T['prop2']
}

Upvotes: 1

twinklecube
twinklecube

Reputation: 94

There are couple of ways to get the type of a property of an object. Some of them are already mentioned and I would like to extend this a bit more.

As a side note, if you use Conditional Types in this task, you can avoid type errors. But that depends on the requrement.

1. Conditinal Types with infer

For example, consider the following object

const student = {
    name: 'John Doe',
    age: 22,
    dormitory: true
}

The task is to get the type of age (for example)

type AgeTypeUsingInfer<T> = T extends {age: infer K} ? K : never;
type AgeType = AgeTypeUsingInfer<typeof student>

AgeType will correctly resolve to number type.

If you pass any type which does not contain the age property, then the returned type will be never.

const musician = {
    art: 'Pop',
    city: 'Gotham'
}

type MusicianAgeType = AgeTypeUsingInfer<typeof musician>

In this case, the type of MusicianAgeType will be resolved to never without type errors.

2. Conditional Types with Indexed Access

type AgeTypeUsingConditionalTypesAndIndexedAccess<T> = T extends {age: any} ? T['age'] : never;

type AgeType2 = AgeTypeUsingConditionalTypesAndIndexedAccess<typeof student>; // number
type MusicianAgeType2 = AgeTypeUsingConditionalTypesAndIndexedAccess<typeof musician>; // never

3. With Indexed Access

type AgeTypeUsingIndexedAccess<T extends {age: any}> = T['age'];

type AgeType3 = AgeTypeUsingIndexedAccess<typeof student>; // number
type MusicianAgeType3 = AgeTypeUsingIndexedAccess<typeof musician>; // type error

Depending on the requirement, you can assign the type to never or let the type error to be surfaced immediately in case if you have passed an object which does not contain the expected property.

Hope this answer is helpful... happy coding :)

Upvotes: 0

flori
flori

Reputation: 15837

Yes, lookup types work just fine:

type BarType = FooType['bar'];

It expects in this case that FooType is an object like:

type FooType = {
    bar: string;
}

It sets BarType to the same type as FooType['bar'], so to a string.

PS: FooType can also be an interface or class.

Upvotes: 179

Oscar
Oscar

Reputation: 2102

Is this what you're looking for?

type PropType<TObj, TProp extends keyof TObj> = TObj[TProp];

and get type of an object property by doing:

type MyPropType = PropType<ObjType, '<key>'>;

which is the same as the way of using Pick in typescript, and it can report compile error if there's any invalid key passed in.

Updates

As @astoilkov suggested, a simpler alternative is PropType['key'].

Upvotes: 127

jcalz
jcalz

Reputation: 327934

Of course, one can get the type of the property by replacing return o[name] into return typeof o[name].

Not really... if you did that:

function getTypeofProperty<T, K extends keyof T>(o: T, name: K) {
    return typeof o[name];
}

You would get a return value of the type "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function", because you're using the JavaScript typeof operator at runtime, which returns a string like "object", not the compile-time type seen by TypeScript.

TypeScript also uses the keyword typeof as the compile-time type query operator. You can tell the difference by looking at the places where it appears. The type query typeof only appears in places where you are writing a type. For example:

const a = { foo: 0 };
const b: Array<typeof a> = [{ foo: 1 }, { foo: 2 }, {foo: 3}]; 
const c: string = typeof a; // "object"

In b, you can see that typeof a appears where you would write a type expression, so it is a TypeScript type query, and Array<typeof a> is evaluated as Array<{foo: number}>. On the other hand, in c, typeof a appears where you would write an value expression, so it is the JavaScript typeof operator, and will evaluate to the string "object" at runtime.


As @k0pernikus mentioned, TypeScript doesn't (and doesn't intend to) allow you to get compile-time type information at runtime. So there is no typeof operator which acts at runtime and returns compile-time information.


Of course, if you want type information about a property at compile time, you can do that, using what's called lookup types. Let's examine the return value of that getProperty() function:

function getProperty<T, K extends keyof T>(o: T, name: K) {
    return  o[name];
} // hover over getProperty to see that the return value is of type T[K]

That T[K] in a type position means "the type of property with a key of type K on an object of type T". Since this is a type-level operation, you can do it at compile-time without declaring any values of the type T or K. For example,

type RegExpFlags = RegExp['flags']; // RegExpFlags is string
const flags: RegExpFlags = 'ig';

Here, you are looking up the "flags" key of the type of RegExp objects and getting back the string type, and you can declare a value of type RegExp['flags'] without having a value of type RegExp anywhere.


That's the closest I can come to answering your question without more information about what you need. Hope that helps. Good luck!

Upvotes: 31

Related Questions