JSON and WebSocket: Johnzon to the rescue


When writing a WebSocket endpoint the first thing you do is to create the endpoint (connection point) then you have to define the protocol to use. Because of what is modern IT world it is quite common to use JSon as payload format for WebSocket messages. Of course you can use any JSon mapper you want or JSON-P directly but you can actually go a bit further and that is what does Johnzon in its coming 0.8-incubating release.

Manual solution

Side note: if you are already familiar with WebSocket (JSR 356) you can surely skip this part.

If you are not familiar with WebSocket (JSR 356) API, here how to write a (very) basic server endpoint:

@ServerEndpoint(value = "/server")
public class ServerEndpointImpl {
    @OnMessage
    public void on(String rawM) {
        // do something clever
    }
}

So if you want a Message instead of a String you need to convert it, it will be something like:

@ServerEndpoint(value = "/server")
public class ServerEndpointImpl {
    @OnMessage
    public void on(String rawM) {
        Message m = new MyMapper().read(rawM);
        // do something clever
    }
}

To avoid to have this glue code in the @OnMessage method (business code) WebSocket API defined Encoder and Decoder API:

public class MyDecoder implements Decoder.TextStream {
    @Override
    public Message decode(Reader reader) throws DecodeException, IOException {
        // TBD
        return null;
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // TBD
    }

    @Override
    public void destroy() {
        // TBD
    }
}
public class MyEncoder implements Encoder.TextStream {
    @Override
    public void encode(Message message, Writer writer) throws EncodeException, IOException {
        // TBD
    }

    @Override
    public void init(EndpointConfig endpointConfig) {
        // TBD
    }

    @Override
    public void destroy() {
        // TBD
    }
}

Then to link these encoder/decoder you just specify them in @ServerEndpoint annotation (note: there is the same in programmatic API but we’ll stick to annotation API in this post):

@ServerEndpoint(value = "/server", encoders = MyEncoder.class, decoders = MyDecoder.class)
public class MyServerEndpoint {
    // same as before
}

Decoder will be used for received messages and encoder will be used for sending messages (yes it means you can receive in json and send in xml).

On client side it is the same but with @ClientEndpoint annotation.

Going further with Johnzon

Mapper

On encoding side it is quite easy to write a custom encoder taking an object and writing it to output stream or as string (there are multiple encoder/decoder types). However since it is alway better when you don’t write the code yourself Johnzon provides you this implementation: org.apache.johnzon.websocket.mapper.JohnzonTextEncoder. To use it just specify it on your endpoint:

@ServerEndpoint(value = "/server", encoders = JohnzonTextEncoder.class)
public class MyServerEndpoint {
    // same as before
}

On the decoder side it already sounds a bit more complicated since you most of the time need a type to know what you deserialize and you don’t have it in a generic decoder.
This is why Johnzon provides a base class just asking you to fill this type when it can’t be guessed…wait guessed? Yes because on server endpoints the framework can find the type without your help. Johnzon decoder is: org.apache.johnzon.websocket.mapper.JohnzonTextDecoder. So on server side you can simply use:

@ServerEndpoint(value = "/server", encoders = JohnzonTextEncoder.class, decoders = JohnzonTextDecoder.class)
public class MyServerEndpoint {
    // same as before
}

However on client side no magic you’ll need to extend JohnzonTextDecoder to provides the type you need:

public class MessageDecoder extends JohnzonTextDecoder {
    public MessageDecoder() {
        super(Message.class);
    }
}

// and used like:

@ClientEndpoint(encoders = JohnzonTextEncoder.class, decoders = MessageDecoder.class)
public class ClientEndpointImpl {
    // ...
}

JSON-P

If you don’t need a custom object mapping and if a JsonArray or a JsonObject (from JSON-P specification) is enough for you, you can use other encoder/decoder from Johnzon to simply map messages on JsonStructures. Implementations are in org.apache.johnzon.websocket.jsr package and are named Jsr[Array|Object|Structure][Encoder|Decoder].

For instance to get a JsonObject you can do:

@ServerEndpoint(value = "/json", encoders = JsrObjectEncoder.class, decoders = JsrObjectDecoder.class)
public class ServerEndpointImpl {
    @OnMessage
    public void on(JsonObject json) {
        // do something clever
    }
}
@ClientEndpoint(encoders = JsrObjectEncoder.class, decoders = JsrObjectDecoder.class)
public class ClientEndpointImpl {
    @OnMessage
    public void on(JsonObject json) {
        // do something clever
    }
}

Here no need of any magic or custom code to do the glue even on client side :).

Leave a comment