Search code examples
javagenericsjacksondeserializationjson-deserialization

Deserialise a generic class , with unknown generic type - Jackson


public class MessageModel<T> {
    T body;
}

Now T could be object of type say Cricket or Football. Now this class is serialised and published to a queue. While consuming, what is the best way to deserialise and identify the message type as Cricket or Football, so that the corresponding service is called. I am using jackson to serialise.

I tried introducing a new property Type during serialisation and during deserialisation will first read as json object, identify the Type and again use jackson to deserialise as the type is known. Ex:

JSONObject jsonObject = new JSONObject(message);
String messageType = jsonObject.optString("message_type");
objectMapper.readValue(message,new TypeReference<MessageModel<messageType>>(){});

But looking for better approaches.


Solution

  • While consuming, what is the best way to deserialise and identify the message type as Cricket or Football, so that the corresponding service is called.

    This isn't possible. Generics don't work that way.

    Generics are involved only at compile-time. When your program runs, the type information from generics is no longer present. There is great information and examples in the Java Tutorial on generics.

    As a brief example, here are two simple classes:

    class MessageModel<T> {
    }
    
    class Cricket {
    }
    

    If we create a new MessageModel<Cricket>, it compiles and runs fine.

    MessageModel<Cricket> cricket = new MessageModel<>();
    

    But at runtime, cricket is identified only as an instance of MessageModel – there is no evidence that the Cricket class was associated in any way. As a bit of evidence, asking the cricket object to report its own class at runtime it will print itself as MessageModel:

    System.out.println("runtime: " + cricket.getClass());
    
    runtime: class MessageModel
    

    This behavior is within a single JVM, but nothing changes if your program serializes an object and sends it to some other program. That other program also has no ability to determine the original Cricket generic type from the serialized object.


    If you need the original class later, you could do something like below, capturing the original class at object construction. Later, when the receiving program needs to know Cricket was the original class, it can call getConcreteClass():

    First, modify MessageModel: require a Class at object construction, save that class as a private member, add getConcreteClass() to expose it:

    class MessageModel<T> {
        private final Class concreteClass;
    
        MessageModel(Class concreteClass) {
            this.concreteClass = concreteClass;
        }
    
        public Class getConcreteClass() {
            return concreteClass;
        }
    }
    

    From there, the original class is avaiable at runtime:

    MessageModel<Cricket> cricket = new MessageModel<>(Cricket.class);
    System.out.println("getClass()         : " + cricket.getClass());
    System.out.println("getConcreteClass() : " + cricket.getConcreteClass());
    
    getClass()         : class MessageModel
    getConcreteClass() : class Cricket