[typescript] Get properties of a class
Is there a way to get properties names of class in TypeScript?
In the example, I would like to ‘describe’ the class A or any class and get an array of its properties (maybe only public ones?), is it possible? Or should I instantiate the object first?
class A < private a1; private a2; /** Getters and Setters */ >class Describer < toBeDescribed:E ; describe(): Array < /** * Do something with 'toBeDescribed' */ return ['a1', 'a2']; //> let describer = new Describer(); let x= describer.describe(); /** x should be ['a1', 'a2'] */
This question is related to typescript reflection
The answer is
compiles to this JavaScript code
That’s because properties in JavaScript start extisting only after they have some value. You have to assign the properties some value.
Still, you cannot get the properties from mere class (you can get only methods from prototype). You must create an instance. Then you get the properties by calling Object.getOwnPropertyNames() .
let a = new A(); let array = return Object.getOwnPropertyNames(a); array[0] === "a1"; array[1] === "a2";
Applied to your example
class Describer < static describe(instance): Array < return Object.getOwnPropertyNames(instance); >> let a = new A(); let x = Describer.describe(a);
Some answers are partially wrong, and some facts in them are partially wrong as well.
Answer your question: Yes! You can.
In Typescript
Generates the following code in Javascript:
var A = /** @class */ (function () < function A() < >return A; >());
as @Erik_Cupal said, you could just do:
let a = new A(); let array = return Object.getOwnPropertyNames(a);
But this is incomplete. What happens if your class has a custom constructor? You need to do a trick with Typescript because it will not compile. You need to assign as any:
let className:any = A; let a = new className();// the members will have value undefined
A general solution will be:
For better understanding this will reference depending on the context.
Another solution, You can just iterate over the object keys like so, Note: you must use an instantiated object with existing properties:
printTypeNames(obj: T) < const objectKeys = Object.keys(obj) as Array; for (let key of objectKeys) < console.log('key:' + key); >>
class A < private a1 = void 0; private a2 = void 0; >class B extends A < private a3 = void 0; private a4 = void 0; >class C extends B < private a5 = void 0; private a6 = void 0; >class Describer < private static FRegEx = new RegExp(/(?:this\.)(.+?(?= ))/g); static describe(val: Function, parent = false): string[] < var result = []; if (parent) < var proto = Object.getPrototypeOf(val.prototype); if (proto) < result = result.concat(this.describe(proto.constructor, parent)); >> result = result.concat(val.toString().match(this.FRegEx) || []); return result; > > console.log(Describer.describe(A)); // ["this.a1", "this.a2"] console.log(Describer.describe(B)); // ["this.a3", "this.a4"] console.log(Describer.describe(C, true)); // ["this.a1", . "this.a6"]
Update: If you are using custom constructors, this functionality will break.
I am currently working on a Linq-like library for Typescript and wanted to implement something like GetProperties of C# in Typescript / Javascript. The more I work with Typescript and generics, the clearer picture I get of that you usually have to have an instantiated object with intialized properties to get any useful information out at runtime about properties of a class. But it would be nice to retrieve information anyways just from the constructor function object, or an array of objects and be flexible about this.
Here is what I ended up with for now.
First off, I define Array prototype method (‘extension method’ for you C# developers).
export < >//creating a module of below code declare global < interface Array< GetProperties(TClass: Function, sortProps: boolean): string[]; > >
The GetProperties method then looks like this, inspired by madreason’s answer.
if (!Array.prototype.GetProperties) < Array.prototype.GetProperties = function (TClass: any = null, sortProps: boolean = false): string[] < if (TClass === null || TClass === undefined) < if (this === null || this === undefined || this.length === 0) < return []; //not possible to find out more information - return empty array >> // debugger if (TClass !== null && TClass !== undefined) < if (this !== null && this !== undefined) < if (this.length >0) < let knownProps: string[] = Describer.describe(this[0]).Where(x =>x !== null && x !== undefined); if (sortProps && knownProps !== null && knownProps !== undefined) < knownProps = knownProps.OrderBy(p =>p); > return knownProps; > if (TClass !== null && TClass !== undefined) < let knownProps: string[] = Describer.describe(TClass).Where(x =>x !== null && x !== undefined); if (sortProps && knownProps !== null && knownProps !== undefined) < knownProps = knownProps.OrderBy(p =>p); > return knownProps; > > > return []; //give up.. > >
The describer method is about the same as madreason’s answer. It can handle both class Function and if you get an object instead. It will then use Object.getOwnPropertyNames if no class Function is given (i.e. the class ‘type’ for C# developers).
class Describer < private static FRegEx = new RegExp(/(?:this\.)(.+?(?= ))/g); static describe(val: any, parent = false): string[] < let isFunction = Object.prototype.toString.call(val) == '[object Function]'; if (isFunction) < let result = []; if (parent) < var proto = Object.getPrototypeOf(val.prototype); if (proto) < result = result.concat(this.describe(proto.constructor, parent)); >> result = result.concat(val.toString().match(this.FRegEx)); result = result.Where(r => r !== null && r !== undefined); return result; > else < if (typeof val == "object") < let knownProps: string[] = Object.getOwnPropertyNames(val); return knownProps; >> return val !== null ? [val.tostring()] : []; > >
Here you see two specs for testing this out with Jasmine.
class Hero < name: string; gender: string; age: number; constructor(name: string = "", gender: string = "", age: number = 0) < this.name = name; this.gender = gender; this.age = age; >> class HeroWithAbility extends Hero < ability: string; constructor(ability: string = "") < super(); this.ability = ability; >> describe('Array Extensions tests for TsExtensions Linq esque library', () => < it('can retrieve props for a class items of an array', () =>< let heroes: Hero[] = [< name: "Han Solo", age: 44, gender: "M" >, < name: "Leia", age: 29, gender: "F" >, < name: "Luke", age: 24, gender: "M" >, < name: "Lando", age: 47, gender: "M" >]; let foundProps = heroes.GetProperties(Hero, false); //debugger let expectedArrayOfProps = ["name", "age", "gender"]; expect(foundProps).toEqual(expectedArrayOfProps); expect(heroes.GetProperties(Hero, true)).toEqual(["age", "gender", "name"]); >); it('can retrieve props for a class only knowing its function', () => < let heroes: Hero[] = []; let foundProps = heroes.GetProperties(Hero, false); let expectedArrayOfProps = ["this.name", "this.gender", "this.age"]; expect(foundProps).toEqual(expectedArrayOfProps); let foundPropsThroughClassFunction = heroes.GetProperties(Hero, true); //debugger expect(foundPropsThroughClassFunction.SequenceEqual(["this.age", "this.gender", "this.name"])).toBe(true); >);
And as madreason mentioned, you have to initialize the props to get any information out from just the class Function itself, or else it is stripped away when Typescript code is turned into Javascript code.
Typescript 3.7 is very good with Generics, but coming from a C# and Reflection background, some fundamental parts of Typescript and generics still feels somewhat loose and unfinished business. Like my code here, but at least I got out the information I wanted — a list of property names for a given class or instance of objects.
SequenceEqual is this method btw:
if (!Array.prototype.SequenceEqual) < Array.prototype.SequenceEqual = function (compareArray: T): boolean < if (!Array.isArray(this) || !Array.isArray(compareArray) || this.length !== compareArray.length) return false; var arr1 = this.concat().sort(); var arr2 = compareArray.concat().sort(); for (var i = 0; i < arr1.length; i++) < if (arr1[i] !== arr2[i]) return false; >return true; > >
How to get class fields of a given type?
I have a class with several methods that make requests to the API. I would like to be able to display a loading indicator for each of these calls independently. I don’t want to add new class field for each loading status indication property, and use one big object instead. The question is: what type should I use for isLoading in my class definition?
export class LibraryStore authors: Author[] = []; books: Book[] = []; isLoading: any /* ? */ = <>; fetchAuthors() this.isLoading.fetchAuthors = true; try // . > catch (e) // . > finally this.isLoading.fetchAuthors = false; > > fetchBooks() // . > >
The Solution
The easiest and most obvious way to make it work is to use Record
Step 1. KeyOfType
export type KeyOfTypeType, ValueType> = keyof [Key in keyof Type as Type[Key] extends ValueType ? Key : never]: any; >;
The above code shows a generic type that extracts keys from Type , where TypeTypescript get class fields is of type ValueType or a derivative thereof.
class SomeObject a = 1; b = 'foo'; c = 'bar'; d = true; > const foobar: KeyOfTypeSomeObject, string>; // 'b' | 'c'
And that’s it. KeyOfType is an answer to the question in title. But let’s take it a little bit further.
Step 2. ClassMethods
export type ClassMethodsT> = KeyOfTypeT, Function>;
Step 3. IsLoadingRecord
export type IsLoadingRecordType> = PartialRecordClassMethodsOmitType, 'isLoading'>>, boolean>>;
export class LibraryStore authors: Author[] = []; books: Book[] = []; isLoading: IsLoadingRecordLibraryStore> = <>; fetchAuthors() this.isLoading.fetchAuthors = true; try // . > catch (e) // . > finally this.isLoading.fetchAuthors = false; > > fetchBooks() // . > >
ClassMethodsOmitType, 'isLoading'>>
ClassMethods type returns all methods from Type , except for isLoading . You may ask, why? If isLoading is not omitted, using it inside Type (like in example above) would end with TypeScript error:
TS2502: 'isLoading' is referenced directly or indirectly in its own type annotation.
RecordClassMethodsOmitType, 'isLoading'>>, boolean>
PartialRecordClassMethodsOmitType, 'isLoading'>>, boolean>>
Code editor shows proper autocomplete hints |
The End
Hope you enjoyed this quick journey. If you have any problem I could help you with, please leave a comment. See you next time!
[typescript] Get properties of a class
Is there a way to get properties names of class in TypeScript?
In the example, I would like to ‘describe’ the class A or any class and get an array of its properties (maybe only public ones?), is it possible? Or should I instantiate the object first?
class A < private a1; private a2; /** Getters and Setters */ >class Describer < toBeDescribed:E ; describe(): Array < /** * Do something with 'toBeDescribed' */ return ['a1', 'a2']; //> let describer = new Describer(); let x= describer.describe(); /** x should be ['a1', 'a2'] */
This question is related to typescript reflection
The answer is
compiles to this JavaScript code
That’s because properties in JavaScript start extisting only after they have some value. You have to assign the properties some value.
Still, you cannot get the properties from mere class (you can get only methods from prototype). You must create an instance. Then you get the properties by calling Object.getOwnPropertyNames() .
let a = new A(); let array = return Object.getOwnPropertyNames(a); array[0] === "a1"; array[1] === "a2";
Applied to your example
class Describer < static describe(instance): Array < return Object.getOwnPropertyNames(instance); >> let a = new A(); let x = Describer.describe(a);
Some answers are partially wrong, and some facts in them are partially wrong as well.
Answer your question: Yes! You can.
In Typescript
Generates the following code in Javascript:
var A = /** @class */ (function () < function A() < >return A; >());
as @Erik_Cupal said, you could just do:
let a = new A(); let array = return Object.getOwnPropertyNames(a);
But this is incomplete. What happens if your class has a custom constructor? You need to do a trick with Typescript because it will not compile. You need to assign as any:
let className:any = A; let a = new className();// the members will have value undefined
A general solution will be:
For better understanding this will reference depending on the context.
Another solution, You can just iterate over the object keys like so, Note: you must use an instantiated object with existing properties:
printTypeNames(obj: T) < const objectKeys = Object.keys(obj) as Array; for (let key of objectKeys) < console.log('key:' + key); >>
class A < private a1 = void 0; private a2 = void 0; >class B extends A < private a3 = void 0; private a4 = void 0; >class C extends B < private a5 = void 0; private a6 = void 0; >class Describer < private static FRegEx = new RegExp(/(?:this\.)(.+?(?= ))/g); static describe(val: Function, parent = false): string[] < var result = []; if (parent) < var proto = Object.getPrototypeOf(val.prototype); if (proto) < result = result.concat(this.describe(proto.constructor, parent)); >> result = result.concat(val.toString().match(this.FRegEx) || []); return result; > > console.log(Describer.describe(A)); // ["this.a1", "this.a2"] console.log(Describer.describe(B)); // ["this.a3", "this.a4"] console.log(Describer.describe(C, true)); // ["this.a1", . "this.a6"]
Update: If you are using custom constructors, this functionality will break.
I am currently working on a Linq-like library for Typescript and wanted to implement something like GetProperties of C# in Typescript / Javascript. The more I work with Typescript and generics, the clearer picture I get of that you usually have to have an instantiated object with intialized properties to get any useful information out at runtime about properties of a class. But it would be nice to retrieve information anyways just from the constructor function object, or an array of objects and be flexible about this.
Here is what I ended up with for now.
First off, I define Array prototype method (‘extension method’ for you C# developers).
export < >//creating a module of below code declare global < interface Array< GetProperties(TClass: Function, sortProps: boolean): string[]; > >
The GetProperties method then looks like this, inspired by madreason’s answer.
if (!Array.prototype.GetProperties) < Array.prototype.GetProperties = function (TClass: any = null, sortProps: boolean = false): string[] < if (TClass === null || TClass === undefined) < if (this === null || this === undefined || this.length === 0) < return []; //not possible to find out more information - return empty array >> // debugger if (TClass !== null && TClass !== undefined) < if (this !== null && this !== undefined) < if (this.length >0) < let knownProps: string[] = Describer.describe(this[0]).Where(x =>x !== null && x !== undefined); if (sortProps && knownProps !== null && knownProps !== undefined) < knownProps = knownProps.OrderBy(p =>p); > return knownProps; > if (TClass !== null && TClass !== undefined) < let knownProps: string[] = Describer.describe(TClass).Where(x =>x !== null && x !== undefined); if (sortProps && knownProps !== null && knownProps !== undefined) < knownProps = knownProps.OrderBy(p =>p); > return knownProps; > > > return []; //give up.. > >
The describer method is about the same as madreason’s answer. It can handle both class Function and if you get an object instead. It will then use Object.getOwnPropertyNames if no class Function is given (i.e. the class ‘type’ for C# developers).
class Describer < private static FRegEx = new RegExp(/(?:this\.)(.+?(?= ))/g); static describe(val: any, parent = false): string[] < let isFunction = Object.prototype.toString.call(val) == '[object Function]'; if (isFunction) < let result = []; if (parent) < var proto = Object.getPrototypeOf(val.prototype); if (proto) < result = result.concat(this.describe(proto.constructor, parent)); >> result = result.concat(val.toString().match(this.FRegEx)); result = result.Where(r => r !== null && r !== undefined); return result; > else < if (typeof val == "object") < let knownProps: string[] = Object.getOwnPropertyNames(val); return knownProps; >> return val !== null ? [val.tostring()] : []; > >
Here you see two specs for testing this out with Jasmine.
class Hero < name: string; gender: string; age: number; constructor(name: string = "", gender: string = "", age: number = 0) < this.name = name; this.gender = gender; this.age = age; >> class HeroWithAbility extends Hero < ability: string; constructor(ability: string = "") < super(); this.ability = ability; >> describe('Array Extensions tests for TsExtensions Linq esque library', () => < it('can retrieve props for a class items of an array', () =>< let heroes: Hero[] = [< name: "Han Solo", age: 44, gender: "M" >, < name: "Leia", age: 29, gender: "F" >, < name: "Luke", age: 24, gender: "M" >, < name: "Lando", age: 47, gender: "M" >]; let foundProps = heroes.GetProperties(Hero, false); //debugger let expectedArrayOfProps = ["name", "age", "gender"]; expect(foundProps).toEqual(expectedArrayOfProps); expect(heroes.GetProperties(Hero, true)).toEqual(["age", "gender", "name"]); >); it('can retrieve props for a class only knowing its function', () => < let heroes: Hero[] = []; let foundProps = heroes.GetProperties(Hero, false); let expectedArrayOfProps = ["this.name", "this.gender", "this.age"]; expect(foundProps).toEqual(expectedArrayOfProps); let foundPropsThroughClassFunction = heroes.GetProperties(Hero, true); //debugger expect(foundPropsThroughClassFunction.SequenceEqual(["this.age", "this.gender", "this.name"])).toBe(true); >);
And as madreason mentioned, you have to initialize the props to get any information out from just the class Function itself, or else it is stripped away when Typescript code is turned into Javascript code.
Typescript 3.7 is very good with Generics, but coming from a C# and Reflection background, some fundamental parts of Typescript and generics still feels somewhat loose and unfinished business. Like my code here, but at least I got out the information I wanted — a list of property names for a given class or instance of objects.
SequenceEqual is this method btw:
if (!Array.prototype.SequenceEqual) < Array.prototype.SequenceEqual = function (compareArray: T): boolean < if (!Array.isArray(this) || !Array.isArray(compareArray) || this.length !== compareArray.length) return false; var arr1 = this.concat().sort(); var arr2 = compareArray.concat().sort(); for (var i = 0; i < arr1.length; i++) < if (arr1[i] !== arr2[i]) return false; >return true; > >