Tuesday, 27 October 2015

Deserialize Runtime Type

In .Net (I guess it's the same in Java), most times that we are going to deserialize an object, we know the concrete type of the object at compile time, so it's not a problem that a Generic constructor for the Serializer object or a Generic method for the Deserialize method is needed. But which are our chances if the specific type won't be known until runtime?

The scenario
Let's say that we have an Interface or a Base class to interact with, but we'll receive the JSON serialized form of one of its implementations or derived classes. For example:


 public abstract class AlgorithmBase
 {
  abstract public int DoCalculation();
 }

 public class SimpleAlgorithm: AlgorithmBase
 {
  public int Coeficient{get;set;}
  public int Var1{get;set;}
  
  public override int DoCalculation()
  {
   Console.WriteLine("SimpleAlgorithm.DoCalculation");
   return 1;
  }
  
 }

This makes perfect sense. Your code will be interacting with AlgorithmBase, but the instance to be deserialized is a SimpleAlgorithm, so on one side you need a way to set in the serialized JSON string the specific type, and on the other side invoke the deserialization code with that type known just at runtime.

It turns that this is pretty easy to achieve with Json.Net. First, let's see our JSON:

{
 "type": "DeserializeRuntimeType.Algorithms.SimpleAlgorithm",
 "data": {
  "coeficient": 5,
  "var1": 8
 }
}

So we need to extract from this JSON the type, and use it to parse the "data" part into an instance of that type. With Json.net we can parse our JSON string into a JSON object in memory (JObject). Furthermore this JObject implements (well, the JToken base class indeed) IDynamicMetaObject, so if used with the dynamic keyword we can conveniently access the different fields with the "." notation. So we can just do this:

dynamic obj = JObject.Parse(jsonString);
string typeStr = obj.type;
JObject jObj = obj.data;

Now, in order to deserialize those data in jObj into an instance of the type in typStr we can use 2 of the overloads of the JObject. The easy way is to use JObject.ToObject(Type, serializer) (I'm using the overload receiving a serializer because of the pascal/camel casing differences between csharp and standard JSON)

var serializer = new JsonSerializer()
   {
       ContractResolver = new CamelCasePropertyNamesContractResolver()
   };

//with a type known at compile time
//AlgorithmBase algorithm = jObj.ToObject(typeof(SimpleAlgorithm), serializer);
Type tp = Type.GetType(typeStr);

AlgorithmBase algorithm = jObj.ToObject(tp, serializer) as AlgorithmBase;

And the quite more complicated one is to invoke the Generic method JObject.ToObject<T>(serializer). Notice that the way to obtain the first MethodInfo when we have several overloads is quite a bit bizarre.

var serializer = new JsonSerializer()
   {
       ContractResolver = new CamelCasePropertyNamesContractResolver()
   };

Type tp = Type.GetType(typeStr);
//with a type known at compile time it would be just:
//AlgorithmBase algorithm = jObj.ToObject<SimpleAlgorithm>(serializer); 
MethodInfo method = typeof(JObject)
    .GetMethods()
    .FirstOrDefault(mt => mt.IsGenericMethod && mt.GetParameters().Count() == 1);
   
   MethodInfo generic = method.MakeGenericMethod(tp);
   
   AlgorithmBase algorithm = generic.Invoke(jObj, new Object[] {serializer}) as AlgorithmBase;

It's interesting to see how the overload that for a type known at compile time is more elegant: jObj.ToObject<SimpleAlgorithm>(serializer), for a type known at run time turns a bit more convoluted.

No comments:

Post a Comment