How to get child classes which implement a certain base class using reflection in Type Script?

后端 未结 3 1761
长情又很酷
长情又很酷 2021-01-05 23:37

Can we use reflection in Type Script just like C# to get the list of classes which implement a certain base class?

For example, let say Snake and Horse implement the

3条回答
  •  北海茫月
    2021-01-06 00:33

    I had the need to deserialize a response from a SOAP web service. I first used an existing SOAP client to get an object from the returned XML and then used a JSON deserializer - the one from http://cloudmark.github.io/Json-Mapping/ - to deserialize this object into the actual types that I want.

    Problem was that one of the returned properties was declared as a base type and could actually contain instances of a number of different sub-types.

    I solved it by creating a class decorator that I apply to the derived types. This class decorator gets 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 a $type property if the XML from the web service contains a type attribute which indicates that polymorphism is in play.

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

    Meta data 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);
    }
    

    Usage:

    // 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 that are passed to the class decorator are the values of the type attribute in the XML response from the web service.

    The code of the deserializer makes use of it like this:

    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 the constructor of the type to deserialize the JSON object to and we replace it with the constructor of the derived type.

    This code currently has the restriction that it adds the metadata only to the direct base class, to to the whole hierarchy. But this could easily be solved by adjusting the code inside RegisterForPolymorphism to walk up the tree.

提交回复
热议问题