Pazvanti Pazvanti - 4 months ago 13
Scala Question

JSON Writes for Java List

I am working on an backend server in Play Framework in Scala. However, I am calling an external library (written in java) that returns a Java list (util.List). I created a writes for the object that is contained in the List, however I don't know how to write the writes for the actual List so that it can be generic (no need to write a "writes" for both List and List, just the writes for A and B).

I know I could use JavaConversions to convert the Java list to a Scala Seq (that already has Writes implemented), but since speed is essential, I would like to not do the extra conversion.

Answer

Here is a possible implementation

import play.api.libs.json.{JsArray, JsValue, Json, Writes}
import scala.collection.JavaConverters._

implicit def jListWrites[A: Writes] = new Writes[java.util.List[A]] {
  override def writes(o: util.List[A]): JsValue = {
    JsArray(o.asScala.map(Json.toJson(_)))
  }
}

You don't create a single Writes but rather a method that can create them for any type that has Writes defined.

You said you want to avoid JavaConversions, but as you can see it is difficult as JsArray expects a Seq[JsValue] anyway so you need to construct a scala Seq one way or another.

What I shown here is more or less equivalent to converting java List to scala mutable.Buffer using asScala and using default Writes for Traversable.

Note that conversions are probably not as expansive as you think, they just create a wrapper, no copying involved.

Here is the best what I could come with in terms of performance

implicit def jListWrites[A: Writes] = new Writes[java.util.List[A]] {
  override def writes(o: util.List[A]): JsValue = {
    val buffer = new Array[JsValue](o.size)
    var i = 0
    while (i < o.size) {
      buffer(i) = Json.toJson(o.get(i))
      i += 1
    }

    JsArray(buffer)
  }
}

It takes 29 ms for 1000000 Ints compared to 39 ms for the straightforward implementation. Note that Int is easy to convert, if your objects are more complex the speedup will be smaller.

Converting 20000 of those case class C(num: Int, n2: Int, s: String) gives equal results (straightforward is even faster by 0.14 ms).