For several of my pet projects, I have had to interpret and/or send JSON WebSocket or HTTP responses in Java. I did some research and picked the library GSON to use. In each project, I have implemented a custom deserializer in order to correctly interpret different packet types.
For example:
{"type": "set-name", "data":{"name":"daniel"}} {"type": "set-timeperiod", "data":{"start":"2017-03-16", "end":"2017-03-17"}}
In this case I would use two classes, SetName
and SetTimePeriod
:
public class SetName implements Data { private String name; @Override public void handle(Foo foo) throws InvalidPacketException { if(name == null) throw new InvalidPacketException(); foo.doThingWithName(name); } @Override public String getType() { return "set-name"; } }
Foo
represents any arbitrary resources, the packet might need to perform its function. The data interface is fairly straightforward:
public interface Data { void handle(Foo foo); String getType(); }
The Data
objects are wrapped in a Packet representing the whole thing:
public void Packet { private String type; private Data data; public Packet(Data data) { this.data = data; type = data.getType(); } public void handle(Foo foo) throws InvalidPacketException { data.handle(foo); } public Packet initData(Data data) { if(this.data != null) throw new IllegalStateException("Data already initialised."); this.data = data; return this; } }
And finally, the packet would be converted from raw JSON to an instance of Packet
using a custom Deserializer.
public class PacketDeserializer implements JsonDeserializer<Packet> { @Override public Packet deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { JsonObject jsonObject = json.getAsJsonObject(); // Remove data object, GSON won't know what to turn it into. JsonObject dataObject = jsonObject.remove("data").getAsJsonObject(); // Determine what type of data object we need. Class dataClass = typeToClass(jsonObject.get("type").getAsString()); // Construct and return a Packet of the correct type. Data data = context.deserialize(dataObject, dataClass); return new Gson().fromJson(jsonObject, Packet.class).initData(data); } private Class<? extends Data> typeToClass(String type) throws JsonParseException { switch(type) { case("set-name"): return SetName.class; case("set-timeperiod"): return SetTimePeriod.class; default: throw new JsonParseException("Type " + type + " is invalid."); } } }
This means GSON will now deserialize the object into a Packet
and then call the handle()
to do whatever needs to be done with that packet:
Packet packet = new GsonBuilder() .registerTypeAdapter(Packet.class, new PacketDeserializer()) .create() .fromJson(message, Packet.class); packet.handle(foo);
In addition, a Packet
instance can be converted to JSON without the need for any custom serializer. Once this structure is built, it is very easy to add new packet types or to send/parse packets.
This seems like an elegant solution to me, but I have some concerns:
- Is it actually efficient? Would it be much faster to parse the JSON directly?
- I can end up with a lot of subclasses of
Data
which makes me feel like there could be a better way of doing it - If this is a good solution, would it be a good thing to use wherever I'm parsing JSON with some object with varying contents like
data
?
I came up with this solution years ago, when I was fairly new to programming and haven't changed it much at all, so I feel like I must be doing something wrong.
String type
?\$\endgroup\$