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
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.