A while ago I wrote an article on how to build a REST api with Spray, where I used the Spray facilities to serialize and deserialize objects to and from JSON.
This article will analyze how we can make the (de)serialization of our case classes easier, by using a library called json4s.
All the code produced in this tutorial can be found here.
Why Json4s?
Although the Spray way of (de)serializing objects works out-of-the-box, it is not always super convenient to use.
In fact, for every entity we need to specify the corresponding JSON format. See for example our QuizProtocol object from the previous tutorial:
// file QuizProtocol.scala ... object Quiz extends DefaultJsonProtocol { implicit val format = jsonFormat3(Quiz.apply) } object Question extends DefaultJsonProtocol { implicit val format = jsonFormat2(Question.apply) } object Answer extends DefaultJsonProtocol { implicit val format = jsonFormat1(Answer.apply) } ... /* We need to do it for each object * and we need to specify the number of fields to serialize */
Json4s is an easy-to-use library that automatically (de)serializes case classes into JSON objects: by using this library we will avoid the need of specifying a (de)serialization format for every case class.
Set up
First of all, we need to import the library by adding the dependencies to our build.sbt file:
// build.sbt libraryDependencies ++= { ... val Json4sVersion = "3.2.11" Seq( ... "org.json4s" %% "json4s-native" % Json4sVersion, "org.json4s" %% "json4s-ext" % Json4sVersion, ... ) }
We now need to tell json4s about any customization we would like in our serializers.
For example, we can define a different date format to use:
// file JsonSupport.scala package com.danielasfregola.quiz.management.serializers import java.text.SimpleDateFormat import org.json4s.ext.JodaTimeSerializers import org.json4s.{DefaultFormats, Formats} import spray.httpx.Json4sSupport trait JsonSupport extends Json4sSupport { implicit def json4sFormats: Formats = customDateFormat ++ JodaTimeSerializers.all val customDateFormat = new DefaultFormats { override def dateFormatter = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss") } }
Also, note that the package json4s-ext offers serializers for types that, although not standard, are quite common. For example, if you use joda.time you will need to add a specific group of serializers, called JodaTimeSerializers.
Json4s Custom Serializers
What happens if you need to serialize a type that is not supported by json4s? Do not dispare! In fact, json4s allows you to specify custom serializers quite easily.
For example, let’s assume that your API uses Timestamp dates and that when, (de)serializing a date in JSON, it should be converted to milliseconds from the unix epoc (i.e.: 1970-01-01 00:00:00 UTC). All you have to do is to create a custom serializer as following:
// file CustomerSerializers.scala package com.danielasfregola.quiz.management.serializers import java.sql.Timestamp import org.json4s.CustomSerializer import org.json4s.JsonAST.{JInt, JNull} object CustomSerializers { val all = List(CustomTimestampSerializer) } case object CustomTimestampSerializer extends CustomSerializer[Timestamp](format => ({ case JInt(x) => new Timestamp(x.longValue * 1000) case JNull => null }, { case date: Timestamp => JInt(date.getTime / 1000) }))
Don’t forget to add your custom serializers as part of your (de)serialization configuration:
// file JsonSupport.scala ... trait JsonSupport extends Json4sSupport { implicit def json4sFormats: Formats = ... ++ CustomSerializers.all ... }
Usage
Now that the set up is completed we just need to extend the JsonSupport trait every time a JSON format for (de)serialization is required.
For example, our QuitProtocol object now looks a lot simpler than before:
// file QuizProtocol.scala package com.danielasfregola.quiz.management import com.danielasfregola.quiz.management.serializers.JsonSupport object QuizProtocol extends JsonSupport { case class Quiz(id: String, question: String, correctAnswer: String) case object QuizCreated case object QuizAlreadyExists case object QuizDeleted case class Question(id: String, question: String) case object QuestionNotFound case class Answer(answer: String) case object CorrectAnswer case object WrongAnswer implicit def toQuestion(quiz: Quiz): Question = Question(id = quiz.id, question = quiz.question) implicit def toAnswer(quiz: Quiz): Answer = Answer(answer = quiz.correctAnswer) }
Summary
Previously, we have described how to create a REST API using Spray. This article has simplified how to perform (de)serialization in Spray, by using the json4s library.
All the code produced in this tutorial can be found here.
2 thoughts on “Spray: how to (de)serialize objects with json4s”