How to get child classes that implement a specific base class using reflection in Type Script?

Is it possible to use reflection in Type Script just like C # to get a list of classes that implement a specific base class?

For example, let's say Snake and Horse implement the base class Animal. Now I need to get classes that implement Animal. Similar to what we can do in C #:

C # equivalent code:

var childTypes = assembly.GetTypes().Where(_ => _.IsSubclassOf(typeof(Animal))); 

Script classes type:

 class Animal { } class Snake extends Animal { } class Horse extends Animal { } 
+5
source share
3 answers

If you are willing to depend on a function that may change soon, because it is based on an outdated specification, and if you are ready to use only classes, not interfaces, you can accomplish this using a decorator.

Here is an example:

<strong> Hierarchy-tracked.ts

 export default function hierarchyTracked(target: new (...args: any[]) => object) { for (const proto of walkPrototypeChain(target)) { if (!Object.hasOwnProperty.call(proto, 'extendedBy')) { const extendedBy: typeof Function.extendedBy = []; Object.defineProperty(proto, 'extendedBy', { get: () => extendedBy }); } // ! is used to suppress a strictNullChecks error on optional. // This is OK since we know it is now defined. proto.extendedBy!.push(target); } } declare global { interface Function { // Declared as optional because not all classes are extended. extendedBy?: Array<new (...args: any[]) => object>; } } function* walkPrototypeChain(target: new (...args: any[]) => object) { let proto = Reflect.getPrototypeOf(target); while (proto && proto !== Object) { yield proto; proto = Reflect.getPrototypeOf(proto); } } 

animals.ts

 import hierarchyTracked from './hierarachy-tracked'; export class Animal { alive = true; static get slayable() {return true;} static extinct = false; } @hierarchyTracked export class Snake extends Animal { isEctotherm = true; } @hierarchyTracked export class Cobra extends Snake { isDeadly = true; } @hierarchyTracked export class Horse extends Animal { isEndotherm = true; } // logs Animal.extendedBy && Animal.extendedBy.map(Taxon => Taxon.name) .forEach(name => { console.log(name); }); // Snake // Cobra // Horse Snake.extendedBy && Snake.extendedBy.map(Taxon => Taxon.name) .forEach(name => { console.log(name); }); // Cobra 

There is no need to resort to a global state, and in fact it is quite neat and explicit.

This also works with Babel 7 if you are not using TypeScript. (note that the same caveats regarding using the decorators mentioned above still apply)

Of course, it is trivial to write manually if you do not want to rely on decorators:

 import trackHierarchy from './hierarachy-tracked'; export class Animal { } class Snake extends Animal { ... } trackHierarchy(Snake); export {Snake}; 

Back to the above code example. It is easily achieved.

He goes from

 var childTypes = assembly.GetTypes().Where(_ => _.IsSubclassOf(typeof(Animal))); 

just

 const childClasses = Animal.extendedBy || []; 

Warning word

If you want to write such code, you need to take a step back and make sure that you really know JavaScript. This type of template and, indeed, your use case, usually indicates that someone came to the language with classical thinking, noticed ES 2015 classes and began to think that they are associated with classes in traditional languages.

ES classes cannot be less like C #, C ++, Java, or Scala classes.

First of all: classes in JavaScript are not types.

Classes in JavaScript are values .

Their declaration form is basically syntactic sugar over prototypes. The pattern you are trying to achieve assumes that you cannot understand it. In particular, this suggests that you might think that they are special.

+4
source

One way to do this is to keep track of all the classes created in one global array:

 var allClasses = [] function register(c) { allClasses.push(c); } register(class Animal {}); register(class Snake extends Animal {}); register(class Horse extends Animal {}); register(class Car {}); 

and when you need classes that inherit from a particular class, just filter out the ones you want:

 Object.values(allClasses).filter((c) => Animal.isPrototypeOf(c)) 

This solution does not apply to Typescript, it will also work with pure JavaScript.

0
source

I needed to deserialize the response from the SOAP web service. I first used the existing SOAP client to get the object from the returned XML, and then used the JSON deserializer - the one from http://cloudmark.imtqy.com/Json-Mapping/ - to deserialize this object into the actual types that I want .

The problem was that one of the returned properties was declared as a base type and could actually contain instances of several different subtypes.

I solved this by creating a class decorator that I apply to derived types. This class decorator receives the base class and applies metadata to it, describing the derived class.

To understand the following code, it is important to know that the XML โ†’ JSON parser adds the $type property if the XML from the web service contains a type attribute that indicates that polymorphism is in the game.

The actual metadata that applies to the base type is the type name from XML and the constructor of the derived type.

Metadata registration and class decorator:

 export interface IJsonPolymorphism { name: string; clazz: {new(): any}; } export function RegisterForPolymorphism(name: string) { return (target) => { let metadata = getJsonPolymorphism(target.__proto__); if (!metadata) { metadata = []; } metadata.push({name: name, clazz: target}); target.__proto__ = Reflect.metadata(jsonPolymorphismMetadataKey, metadata)(target.__proto__); return target; }; } export function getJsonPolymorphism(target: any): IJsonPolymorphism[] { return Reflect.getMetadata(jsonPolymorphismMetadataKey, target); } 

Using:

 // The base class export class PropertyBase { /*...*/ } // The derived classes // @RegisterForPolymorphism("b:PicklistProperty") export class PicklistProperty extends PropertyBase { /*...*/ } @RegisterForPolymorphism("b:TextProperty") export class TextProperty extends PropertyBase { /*...*/ } 

The strings passed to the class decorator are the values โ€‹โ€‹of the type attribute in the XML response from the web service.

The deserializer code uses it as follows:

 if (jsonObject["$type"]) { const polymorphism = getJsonPolymorphism(clazz); if (polymorphism && polymorphism.filter) { const subclassDefinition = polymorphism.filter(x => x.name === jsonObject["$type"])[0]; if (subclassDefinition && subclassDefinition.clazz) { clazz = subclassDefinition.clazz; } } } 

Basically, clazz is a type constructor for deserializing a JSON object, and we replace it with a constructor of a derived type.

This code currently has the limitation that it only adds metadata to the direct base class, to the entire hierarchy. But this can be easily solved by editing the code inside RegisterForPolymorphism to approach the tree.

0
source

Source: https://habr.com/ru/post/1264618/


All Articles