I have the following code:
import play.api.libs.json._
object Test {
sealed trait T
case class A(s: String) extends T
implicit val writesA: OWrites[A] = Json.writes[A]
implicit val writesT: OWrites[T] = Json.writes[T]
def main(args: Array[String]): Unit = {
val x = A("str")
println(Json.toJson[T](x)(writesT))
}
}
It causes a StackOverflowError
when run.
If I add lazy
in front of writesT
, the StackOverflowError
goes away and everything works:
import play.api.libs.json._
object Test {
sealed trait T
case class A(s: String) extends T
implicit val writesA: OWrites[A] = Json.writes[A]
implicit lazy val writesT: OWrites[T] = Json.writes[T]
def main(args: Array[String]): Unit = {
val x = A("str")
println(Json.toJson[T](x)(writesT))
}
}
The StackOverflowError
also disappears when I move the implicit
s into the main
function:
import play.api.libs.json._
object Test {
sealed trait T
case class A(s: String) extends T
def main(args: Array[String]): Unit = {
implicit val writesA: OWrites[A] = Json.writes[A]
implicit val writesT: OWrites[T] = Json.writes[T]
val x = A("str")
println(Json.toJson[T](x)(writesT))
}
}
Can anyone explain to me why I get a StackOverflowError
in the first case?
My suspicion is that it has something to do with initialization orders and the macros that play-json uses in the background. But if that is the case I don't get why using lazy
helps, because the code should still be generated at compile time and simply evaluating it later at runtime shouldn't change anything. Apparently in the later cases the writesA
instance is found by writesT
but not in the first case. Why does adding lazy
resolve a compile-time issue with the resolution of implicits and macro code generation?
Or is this an issue on a completely different level?
I am using Scala 2.12.3 and play-json 2.6.2.