Gson - Custom deserializer for polymorphic variables

I am using gson to convert java objects to and from json objects. I am facing a polymorphism problem.

I have these requests that look something like this:

{
  "method": "getUser",
  "methodParameters": {
    "a": "b",
    "c": "d",
    "e": "f",
    "data": {
      "u": "v",
      "x": "y"
    }
  },
  "version": "1.3"
}

      

Each request method has a different type of data object. Naturally, each data object extends a base class named RequestData.

I tried to create a custom deserializer, but since it is a request object and not a data object that supports this method, I couldn't find a way to find out which object to deserialize.

Is it possible in some way to get the value of the method when deserializing the data object, or is there another better way to solve this problem? Thank.

+3


source to share


2 answers


I ran into a similar problem: as Tomek pointed out, you will need a custom deserializer as well as a specific JSON field that you will use at runtime to decide which subclass should be instantiated.

Consider the following class as a base class:

// Base class for a server sent event
public class ServerEvent 
{
    public static final String TYPEFIELDNAME = "eventType";

    // Event type is used during deserialization to choose the right subclass
    private final String eventType;

    public ServerEvent(String eventType) 
    {
        this.eventType = eventType;
    }

    public String toString()
    {
        return "eventType: " + eventType;
    }
}

      

ServerEvent has two subclasses, each with different properties:

public class EventA extends ServerEvent 
{   
    private static final String EVENTTYPE = "eventa";
    private int data;

    public EventA() 
    {
        super(EVENTTYPE);
    }

    // Helper function to register this class with ServerEventDeserializer
    public static void register(ServerEventDeserializer deserializer) 
    {
        deserializer.registerEvent(EVENTTYPE, EventA.class);
    }

    public String toString()
    {
        return super.toString() + ", data = " + data;
    }
}

public class EventB extends ServerEvent 
{   
    private static final String EVENTTYPE = "eventb";
    private String name;

    public EventB() 
    {
        super(EVENTTYPE);
    }

    // Helper function to register this class with ServerEventDeserializer
    public static void register(ServerEventDeserializer deserializer) 
    {
        deserializer.registerEvent(EVENTTYPE, EventB.class);
    }

    public String toString()
    {
        return super.toString() + ", name = " + name;
    }
}

      



Two possible inputs could be as follows:

{ "eventType" : "eventa", "data" : 15 }
{ "eventType" : "eventb", "name" : "test" }

      

This is a polymorphic deserializer:

// This class handles the polymorphic deserialization of ServerEvent class
public class ServerEventDeserializer implements JsonDeserializer<ServerEvent>
{
    // Gson engine
    private Gson gson;

    // Map of subclasses
    private Map<String, Class<? extends ServerEvent>> eventRegistry;

    public ServerEventDeserializer()
    {
        gson = new Gson();
        eventRegistry = new HashMap<String, Class<? extends ServerEvent>>();
    }

    // Registers a ServerEvent subclass
    public void registerEvent(String event, Class<? extends ServerEvent> eventInstanceClass)
    {
        eventRegistry.put(event, eventInstanceClass);
    }

    @Override
    public ServerEvent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
    {
        try
        {
            // Get the JSON object
            JsonObject commandObject = json.getAsJsonObject();

            // Read the field named "ServerEvent.TYPEFIELDNAME"
            JsonElement commandTypeElement = commandObject.get(ServerEvent.TYPEFIELDNAME);

            // Query the eventRegistry map to instance the right subclass
            Class<? extends ServerEvent> commandInstanceClass = eventRegistry.get(commandTypeElement.getAsString());
            ServerEvent command = gson.fromJson(json, commandInstanceClass);

            return command;
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }
}

      

I wrote a minimal working example that can be downloaded here .

+2


source


When deserializing this example, the custom json string deserializer will have this method header:

public YOUR_CLASS deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

      



(add this deserializer with the registerTypeAdapter () method to the GsonBuilder object)

You can convert the JsonElement to a string, extract your methodsParameters and based on something typical of each RequestData subclass, you can instantiate a specific class. Let's say for example that we have MyRequestData, this class has 10 parameters. I check the json string and if it has so many parameters then return that specific instance. Of course, you can first deserialize the Parameters method (in the deserializer class) to the Map class, and then parse it to pick the correct class.

0


source







All Articles