Reputation: 2956
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
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
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
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
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
Reputation: 327934
Of course, one can get the type of the property by replacing
return o[name]
intoreturn 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