Reputation: 796
To start off, I have looked at a few other answers for similar questions, but they do not answer my particular situation.
I'm parsing JSON messages which consist of a body and a header, where the header stores what type of object the body is:
{
"body": {
"eventName": "someEventName"
},
"header": {
"purpose": "event"
}
}
In Java, I've modeled this structure using the following classes:
public class Message {
public Body body;
public Header header;
}
public class Header {
public String purpose; // Marks what child class the body of the message uses
}
public abstract class Body {
// Child classes store additional fields
}
// Example implementation of the body class
public class EventBody extends Body {
public String eventName; // Name of some event
}
After doing some research, I found that RuntimeTypeAdapterFactory
is normally used to parse/write polymorphic objects; however, the RutimeTypeAdapterFactory
class relies on the type being stored in the base class of the polymorphic object (i.e. Body
). But in this scenario, that's not the case ― the type is stored in another object, Header
.
What would be the best way to go about parsing these kind of objects? I'd like to avoid having to write a custom Serializer
/Deserializer
for compactness, but I wouldn't mind writing them if it's necessary.
Upvotes: 0
Views: 2239
Reputation: 796
I realize that asking for a solution that doesn't involve a custom Serializer
/Deserializer
is a bit ridiculous, as this is exactly the type of scenario they'd be used in (I was thinking I could get away with a custom TypeAdapterFactory
, but using a Serializer/Deserializer is easier).
Anyway, for my scenario, a combination of a custom Serializer/Deserializer for the Message class seems to work fine. Since I already use an enum to track different message purposes and their string names, I decided to simply add an additional field to that enum to store the corresponding body class.
MessagePurpose Enum:
public enum MessagePurpose {
EVENT("event", EventBody.class);
public final String purposeName;
public final Class bodyClass;
MessagePurpose(String purposeName, Class classi) {
this.purposeName = purposeName;
bodyClass = classi;
}
}
MessageSerializer:
public class MessageSerializer implements JsonSerializer<Message> {
@Override
public JsonElement serialize(Message message, Type type, JsonSerializationContext jsc) {
if(message == null) {
return null;
}
JsonObject messageObj = new JsonObject();
// Get the class representing the body object from the purpose enum
Class bodyClassType = message.getPurpose().bodyClass;
messageObj.add("body", jsc.serialize(message.getBody(), bodyClassType));
messageObj.add("header", jsc.serialize(message.getHeader(), Header.class));
return messageObj;
}
}
MessageDeserializer:
public class MessageDeserializer implements JsonDeserializer<Message> {
@Override
public Message deserialize(JsonElement je, Type type, JsonDeserializationContext jdc) throws JsonParseException {
Header header = jdc.deserialize(je.getAsJsonObject().get("header"), Header.class);
// Get the class representing the body object from the purpose enum
Class bodyClassType = header.getPurpose().bodyClass;
Body body = jdc.deserialize(je.getAsJsonObject().get("body"), bodyClassType);
return new Message(body, header);
}
}
Main function to test with:
public static void main(String[] args) {
GsonBuilder gb = new GsonBuilder();
// Register the Message class since I need to access info in the header
gb.registerTypeAdapter(Message.class, new MessageDeserializer());
gb.registerTypeAdapter(Message.class, new MessageSerializer());
Gson gson = gb.setPrettyPrinting().create();
EventBody event = new EventBody(EventType.SOME_EVENT_NAME);
String eventJson = gson.toJson(event.getAsMessage());
System.out.println(eventJson);
Message newEvent = gson.fromJson(eventJson);
System.out.println("\nEvent type: " + ((EventBody) newEvent.getBody()).getEventName());
}
The above test class prints:
{
"body": {
"eventType": "someEventName"
},
"header": {
"purpose": "event"
}
}
Event Type: someEventName
This output matches the JSON of the Messages I'm parsing, and it seems to deserialize different types of messages just fine.
Upvotes: 2