Let’s Play! - My Play Framework Learning Journey (10) - Using Jackson Directly for Json Processing

In the update one week ago to my previous post on Json processing, I mentioned that Play Json’s macro performance is not as good as using a manual serializer. Lightbend has done something to that since then. Lightbend support also advised that if performance is of the utmost concern, one can use Jackson directly. I tried that and found that using Jackson directly is noticeably faster than using Play Json, even with manual serializer.

Below is what I did. It mostly follows this post, with some small variations.

In terms of dependencies, I aso need the jackson-datatype-jsr310 dependency for dealing with Java 8 LocalDate.

libraryDependencies ++= Seq(
  "com.fasterxml.jackson.core" % "jackson-databind" % "2.8.8.1",
  "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.8.8",
  "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.8.8"
)

Plus I need to register the JavaTimeModule to Jackson’s ObjectMapper, and configure the mapper to produce the right date format (I want it in YYYY-MM-DD format). I also added an implicit function to convert domain objects to Play’s Writeable class so that it can work seamlessly with Action Results.

object JsonUtil {
  val mapper = new ObjectMapper() with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)
  mapper.registerModule(new JavaTimeModule)
  mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
  mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

  def toJson(value: Map[Symbol, Any]): String = {
    toJson(value map { case (k,v) => k.name -> v})
  }

  def toJson(value: Any): String = {
    mapper.writeValueAsString(value)
  }

  def toJsonBytes(value: Any): Array[Byte] = {
    mapper.writeValueAsBytes(value)
  }

  def toMap[V](json:String)(implicit m: Manifest[V]) = fromJson[Map[String,V]](json)

  def fromJson[T](json: String)(implicit m : Manifest[T]): T = {
    mapper.readValue[T](json)
  }

  implicit def toWriteable[T]: Writeable[T] = Writeable[T](
    (obj: T) => ByteString(JsonUtil.toJsonBytes(obj)), Some(ContentTypes.JSON)
  )
}

After that I can use JsonUtil.toJson or JsonUtil.toJsonBytes instead of Play’s Json.toJson method. If so, I also need to set the content-type to Json by calling Result.as(JSON) method:

def getAll = Action.async {
  repo.findAll map { result => Ok(JsonUtil.toJson(result)).as(JSON) }
}

Or, if you prefer to use implicit conversions, you can put the implicit toWriteable method in scope, so that it will convert your domain objects to Writeable to save more boilerplate code.

  def getAll = {
    Action.async {
      repo.findAll map { result => Ok(result) }
    }
  }

This however means that you can no longer use some of the helper methods provided by Play for testing. You have to write your own logic to deal with the Json values using JsonUtil, or you can keep the Play Json formatters and use them just for testing.

For example, you can define this helper method to replace contentAsJson:

def parseResultJson[T: Manifest](of: Content): T = JsonUtil.fromJson[T](of.body)

Additional References

Go Top
comments powered by Disqus