In the previous article, we looked at creating a WebSocket server using play framework. This server relays back anything we sent to it as a text message and acts as a Chat Server. However, in the real world, you would need much more structured information to be passed as the chat message in order to add additional meta information.

JSON Protocol

Let's look at how we can use WebSocket support in Play Framework to send and receive JSON messages. For the use case, we have assumed the following sample JSON structure for communication.

{
  "from": {
    "id": "123",
    "name": "Love"
  },
  "recipient": {
    "id": "789",
    "name": "Bot"
  },
  "text": "Hello buddy, you there.",
  "timestamp": 1538360260,
  "conversationId": "3123fda525sdfas234ads"
}

The above JSON structure looks ideal from a chat perspective. We are sending in additional information like the sender, recipient, timestamp etc. along with text payload.

PlayFramework native support of JSON is equipped with Play JSON. So, let's write the data types of the above JSON and the respective protocol to convert to-and-from JSON messages. You can read more about the Play JSON support here.

JSON support with PlayFramework WebSocket

In the last article, we created a route to use the text-based WebSocket endpoint, let's define a new route for handling the new endpoint.

GET        /wsJson           controllers.ChatController.socketJson

Play Framework WebSocket.accept API creates a WebSocket handler that allows us to plug in an Akka Stream flow to process the streaming incoming messages and respond back as the output of the same stream. This has been already explained here. However, what we didn't discuss is that the API accepts a couple of standard input/output message types including String, ByteString, Array[Byte], JsValue etc.

With that said, we can easily set up the WebSocket endpoint to expect the JSON payload. This can be defined using the JsValue abstract type.

As per the ActorFlow.actorRef API, the underlying actor will receive new messages sent to the WebSocket endpoint. In the above case, this would be of type JsValue. The Actor handler has to now start expecting the messages of type JsValue. We used the above-defined JSON protocol to convert the JsValue messages to the case classes, process the result and send back the processed JsValue. Here's the snippet.

Testing the WebSocket endpoint

For testing the WebSocket endpoint using the JSON payloads, we use the Smart WebSocket Client for chrome. Here's the snapshot with the JSON payload of the chat message. We receive back the echo payload with a similar text message.

Play Framework WebSocket Server with JSON payload

Adding Message Flow Transformers

Cool, it all worked fine so far. However, what we have done here is accepting the JsValue as the input to WebSocket and responding back with JsValue to the WebSocket connection again. This is fine, but we have to manually validate the incoming JSON and transform it to respective case classes and to repeat the same logic back when responding back as JSON. WebSocket.accept API does provide support for extending the message format support for any abstract data types.

WebSocket.accept API expects an implicit MessageFlowTransformer to provide the message transformers to and from the serialized WebSocket streams. There is default support for JsValue, String, Array[Byte] etc. However, we need to extend it to support our custom abstract types here, viz. ConversationMessage. This is quite simple though.

implicit val messageTransformer = MessageFlowTransformer.jsonMessageFlowTransformer[ConversationMessage, ConversationMessage]

The above snippet defines an implicit MessageFlowTransformer in the scope which takes ConversationMessage as input and output. You can obviously define different input and output entities. The above snippet expects the JSON formats in the scope which we already defined above. Here's the updated clean snippet of the entire implementation.

Checkout the complete source code here.