So now that I have a custom game server connected to a message-based infrastructure, I need a way to send messages back and forth to other parts of the system. Messages going to and from the custom game server are transmitted as JSON given that the format is easy to work with, supported in all the languages we’re immediately concerned with, and more or less straightforward.
For my Java-based server I am using Jackson and Jackson-databind to magically turn JSON into POJOs and back.
Message Formatting
Let’s consider a more interesting version of our Message: Along with other basic message information (here represented by the not-very-interesting property “text”) a Message contains a Body, of which there are many possible implementations for Body.
public class Message {
public String text;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="bodyType")
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = BeginBattleRequest.class),
@JsonSubTypes.Type(value = BeginBattleResponse.class),
@JsonSubTypes.Type(value = GetNameRequest.class),
@JsonSubTypes.Type(value = GetNameResponse.class)
})
public Object body;
public Message() {
}
public Message(String text) {
this.text = text;
}
}
Jackson Databinding uses the type information of the body to figure out what value to put in a property we’ve decided to call “bodyType”. Given this information, Jackson can figure out how to reconstruct the objects when given the JSON string this serializes to:
{
"text":"fight!",
"body":{
"bodyType":"BeginBattleRequest",
"opponentName":"thatGuyOverThere"
}
}
This works really well, especially if the list of types you’re going to serialize into “body” is manageable and short, and also is known up front at compile time.
Discovering Types
But what if your types aren’t known up front, or you have hundreds of these body classes (and are adding more all the time), as is the real-world implementation of this case?
Instead, we can define the ObjectMapper sub types programmatically, using Reflection to identify our body candidate classes. I prefer to use an annotation to denote the classes of interest, but you have each of the body classes implement an interface or extend from some body base class, whatever is most suitable for your situation.
@MessageBody
public class BeginBattleResponse {
public boolean successful;
public Result result;
public enum Result {
OK,
INVALID_REQUEST
// etc, etc etc...
}
}
Then registering these @MessageBody classes with the ObjectMapper is simple:
objectMapper = new ObjectMapper(new JsonFactory());
objectMapper.disableDefaultTyping();
objectMapper.disable(SerializationFeature.WRITE_NULL_MAP_VALUES);
objectMapper.disable(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS);
// register our @MessageBody classes with the objectMapper:
for(Class<?> clazz : reflections.getTypesAnnotatedWith(MessageBody.class)) {
objectMapper.registerSubtypes(clazz);
}
The Code
The full implementation of this is at https://github.com/trasa/WebSocketClientServer/