Monday, 27 October 2014

Deserialize object trees from json in Java

The worst kind of problems in software are those that are hard to search for in Google, in my case everything I tried was some variation of "object", "java" and "json".

I am trying to implement a spec in an Android app that needs to deserialize some json. Unfortunately, I do not know exactly what this json will look like since that is not in the spec and getting an answer will take too long. I need to start something now, even if I need to change it later.

The issue with the json is this: I have a request object that is serialized, but one of its fields, called "args" is an Object. That is because it can be one of a number of different classes depending on the value of another field "requestType". In other words, if requestType is Commit, the args will be of type CommitIn, if requestType is Register, args will be RegisterIn etc.

I started using gson by Google and although it is easy to deserialize the request itself, clearly the gson builder class can not know what type to deserialize for args. To make it worse, the request classes do not all inherit from the same class (although I could make it so that they could do if that would help).

I've read about RuntimeTypeConverter but this seems to imply that the serialized classes must all inherit from the same class and more importantly, that they have some kind of "type" field that the converter can use to know what concrete type to deserialize into. I can't find out whether this type information will be present.

I've come up with a variety of possible solutions but I'm not sure which is the best going forwards:

  1. Use the raw JSON, find the requestType and then modify the JSON to inject a type parameter which can then be used by the json deserializer. Crude but would work.
  2. Deserialize the request into a parent request object to work out the requestType and then deserialize again into a specialized subclass once the requestType is know, the type converter will then have a target concrete type. The messages are fairly small so the overhead should not be too bad?
  3. Extend the json classes so that it is possible to map the value of requestType into the conversion of the args object - ideally done in a generic kind of way something like ConvertTypeByProperty(targetField, propertyName, "Commit" CommitIn.class()).
  4. Create a type convertor for the top level object and do everything in a customised kind of way (although this kind of defeats the point of using a library I think).
I'm not sure how to proceed, any suggestions?

EDIT

I ended up doing #4 but I didn't have to write my own converter or anything like that. By making each request a separate class with the correct type for "args" and each inheriting the other fields from a parent request class, what I could then do was use the built-in RuntimeTypeAdaptor class (actually it isn't included in the gson library for some reason but the source is downloadable from Google) and then all I have to do is point the type adaptor to the requestType to identify the type of request that is coming in, all the deserialisation then works correctly.

ASMRequest is the parent class and ASMCommitRequest is one of its subclasses

RuntimeTypeAdapter requestAdapter = RuntimeTypeAdapter.create(ASMRequest.class, "requestType");
requestAdapter.registerSubtype(ASMCommitRequest.class, "Commit");   // 1 example of specific request class if requestType equals "Commit"

 Gson gson = new GsonBuilder()
                    .registerTypeAdapter(ASMRequest.class, requestAdapter)
                    .create();
Post a Comment