JSON in Play Framework - Advanced Libraries
This is a followup post to my previous one covering JSON in Play framework. I’d like to show how the manual work I did before in trying to make JSON mapping compatible with external API can be done by using 2 small but useful libraries:
-
[play-json-naming](https://github.com/tototoshi/play-json-naming)
-
[play-json-extensions](https://github.com/xdotai/play-json-extensions)
play-json-naming
This is a very simple library that can be used to convert from camelCase
formatting (the default one that we use in Scala) to snake_case
formatting that is common in various different languages (for example PHP or Ruby). There is a vast amount of APIs that follow that convention, examples include GitHub or Twitter APIs.
This is how you can use it:
import com.github.tototoshi.play.json.JsonNaming
implicit val playerWrites = JsonNaming.snakecase(Json.writes[Player])
This is enough to provide a mapping from a case class like this:
case class Player(
id: Int,
name: String,
status: String,
version: String,
stack: Int,
bet: Int,
holeCards: List[Card])
to a JSON in the following format:
{
"id": 1,
"name": "wojtek test",
"status": "active",
"version": "test version",
"stack": 3,
"bet": 32,
"hole_cards": [
{
"rank": "A",
"suit": "spades"
},
{
"rank": "10",
"suit": "spades"
}
]
}
As you can see holeCards
was replaced with hole_cards
. That’s pretty much all that library does.
This is the project page: https://github.com/tototoshi/play-json-naming. Over there you can find information how to include it in your project.
This is the commit I did to introduce the change in my own project, as you can see I have removed a bunch of type-unsafe code, and replaced it with 2 method calls.
play-json extensions
This library is definitely more comprehensive than previous one, it consists of few (de)serializers which can be used in your project. It’s main feature is case class serializer that can handle more than 22 fields in one class (common limitation in Scala), but I’ll show one other feature I had to use to make my API compatible with external service.
Unfortunately it’s documentation isn’t really comprehensive and there are few mistakes (probably related to recent migration to new package name).
I’d like to show you one particular use case in which I was able to use it, namely: “De-/Serialize single value classes”
The problem is that in a setup like this:
sealed abstract class Suit
case object Clubs extends Suit
case object Spades extends Suit
case object Hearts extends Suit
case object Diamonds extends Suit
sealed abstract class Rank
case object Two extends Rank
case object Three extends Rank
...
case class Card(rank: Rank, suit: Suit)
...
implicit val suitFormat = Json.format[Suit]
implicit val rankFormat = Json.format[Rank]
implicit val cardFormat = Json.format[Card]
The resulting JSON for class Card
will look like this:
{
"suit": {
"suit": "Spades"
},
"rank": {
"rank": "A"
}
}
And our expected format is as follows (scroll up to see complete example):
{
"rank": "A",
"suit": "Spades"
}
In the previous post I have shown how this can be done by writing manual mapping from case class to JsValue (JsString in this case).
The same result can be achieved with the help of play-json-extensions in the following way:
import ai.x.play.json.Jsonx
implicit val suitFormat = Jsonx.formatInline[Suit]
implicit val rankFormat = Jsonx.formatInline[Rank]
implicit val cardFormat = Json.format[Card]
One note though: There is a problem in the library itself and I had to make to my domain objects definitions, the Suit was a sealed abstract class
, now it has to be just sealed class
. In my opinion this is a small workaround that is acceptable in my scenario, and hopefully the library will be fixed/extended to allow for this case.
Take a look at the commit I did to introduce the change, as you can see I have removed a bunch of lines which were responsible for manual JSON mappings.
This is the library page: https://github.com/xdotai/play-json-extensions
Summary
I have show how you can use 2 more advanced Play JSON libraries to achieve your goal of making a compatible APIs. This is a more reasonable approach than writing mappings yourself and also it should be less error prone.