Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gson can't deserialize inherited class?

Tags:

java

json

gson

I have a simple Json structure like:

{"MessageType":"TimeData","TimeData":{"hh":12,"mm":13,"ms":15,"ss":14}}

and I devised the following classes to deserialize it:

public class JsonMessage
{
    public enum MessageTypes{
        WhoAreYou,
        TimeData
    }
    JsonMessage(){
    }
    public MessageTypes MessageType;
}
class TimeData extends JsonMessage{
    int hh;
    int mm;
    int ss;
    int ms;

    TimeData() {
    }    
}

I need to split deserialization into tow phases:

1- deserialize to read the MessageType.

2- proceed with the rest of deserialization based on the MessageType

The code is straightforward:

public void dispatch(Object message, IoSession session)
    {

            Gson gson = new Gson();
            JsonMessage result = gson.fromJson(message.toString(), JsonMessage.class);
            System.out.println(result.MessageType.toString());
            switch (result.MessageType)
                {
                    case WhoAreYou:{
                    //.....
                    break;
                    }
                    case TimeUpdate:
                        TimeData res = new Gson().fromJson(message.toString(), TimeData.class);
                        System.out.println(res.hh);
                        break;
                    default:break;
        }
    }

My Program can enter the correct switch-case(which is TimeUpdate) but it doesn't parse it correctly (The println prints 0 instead of 12)

where do you think I have done something wrong? thank you

like image 244
rahman Avatar asked Apr 25 '13 16:04

rahman


2 Answers

I managed to solve this similar problem by implementing a custom JsonDeserializer in the following way.

First you attach to your enum the subclasses based on the type and a method to retrieve the correct Class<?> according to the enum name:

enum MessageType {
  WHO_ARE_YOU(WhoAreYou.class),
  TIME_UPDATE(TimeUpdate.class);

  public final Class<?> clazz;

  MessageType(Class<?> clazz) { this.clazz = clazz; }

  public static MessageType forName(String name) {
    for (MessageType t : values())
      if (name.equals(t.name()))
        return t;

    return NULL;
  }
}

Then in the deserialize method I did the following:

public JsonMessage deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
   JsonObject object = json.getAsJsonObject();
   String kind = object.get("messageType").getAsString();
   Class<?> clazz = MessageType.forName(kind).clazz;
   JsonMessage result = null;

   try {
     result = (JsonMessage)clazz.newInstance();
     Field[] fs = clazz.getFields();

     for (Field f : fs) {
       Object value = context.deserialize(object.get(f.getName()), f.getType());                 
       if (value != null)
         f.set(result, value);
     }
   } 
   catch (Exception e) {
     e.printStackTrace();
   }
}

Everything is managed by reflection so that a correct object is created and then all fields are deserialized accordingly.

I had a complex hierarchy of objects so I preferred to go this way to let the gson deserializer manage everything. Of course you will need to register the serializer with the gson parser instance.

A NOTE: Your naming of things is quite incorrect according to Java standards. enum constants should be ALL_CAPITALIZED, enum class names should be singular (eg. MessageType). Instance variables should be camelcased (eg. messageType not MessageType)

like image 27
Jack Avatar answered Oct 01 '22 11:10

Jack


The issue is that your JSON represents an Object that contains another object you're interested in while your Java is just a single object.

You can actually just write deserializers for each type and use them once you determine the MessageType:

public static void main(String[] args)
{
    Gson gson = new GsonBuilder().registerTypeAdapter(TimeData.class, new TimeDataDeserializer()).create();
    String json = "{\"MessageType\":\"TimeData\",\"TimeData\":{\"hh\":12,\"mm\":13,\"ms\":15,\"ss\":14}}";
    JsonMessage message = gson.fromJson(json, JsonMessage.class);

    switch(message.MessageType)
    {
        case TimeData:
            TimeData td = new GsonBuilder()
                            .registerTypeAdapter(TimeData.class, new TimeDataDeserializer())
                            .create()
                            .fromJson(json, TimeData.class);
            td.MessageType = message.MessageType
            System.out.println(td.hh);
            break;
        default:
            break;
    }
}

class TimeDataDeserializer implements JsonDeserializer<TimeData>
{
    @Override
    public TimeData deserialize(JsonElement je, Type type, JsonDeserializationContext jdc)  
        throws JsonParseException
    {
        JsonObject jo = je.getAsJsonObject().getAsJsonObject("TimeData");
        Gson g = new Gson();
        return g.fromJson(jo, TimeData.class);
    }
}
like image 113
Brian Roach Avatar answered Oct 01 '22 12:10

Brian Roach