Сгенерированные json неявные экземпляры вызывают StackOverflowError - PullRequest
1 голос
/ 07 ноября 2019

У меня есть следующий код:

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))
   }
}

Это вызывает StackOverflowError при запуске.

Если я добавлю lazy перед writesT, StackOverflowError исчезнет, ​​и все будет работать:

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))
   }
}

StackOverflowError также исчезает, когда я перемещаю implicit s в функцию main:

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))
   }
}

Может кто-нибудь объяснить мне, почему я получаю StackOverflowError в первом случае?

Я подозреваю, что это как-то связано с порядком инициализации и макросами, которые play-json использует в фоновом режиме. Но если это так, я не понимаю, почему использование lazy помогает, потому что код должен все еще генерироваться во время компиляции, и простая оценка его позже во время выполнения не должна ничего менять. По-видимому, в более поздних случаях экземпляр writesA найден writesT, но не в первом случае. Почему добавление lazy решает проблему времени компиляции с разрешением последствий и генерацией макрокода?

Или это проблема совершенно другого уровня?

Я использую Scala 2.12.3 и play-json 2.6.2.

1 Ответ

0 голосов
/ 07 ноября 2019

Это прекрасно работает с Play JSON 2.7.x

import play.api.libs.json._

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]

val x = A("str")
println(Json.toJson[T](x)(writesT))

// Exiting paste mode, now interpreting.

scala> println(Json.toJson[T](x)(writesT))
                                   ^
{"s":"str","_type":"$line2.$read.$iw.$iw.A"}

Думаю, это связано с "упадком контравариантности" на Writes неявном исправленном с тех пор.

Обходной путь для Play JSON 2.6 - реализовать экземпляр Writes (или OFormat) для закрытой черты вручную.

scala> :paste
// Entering paste mode (ctrl-D to finish)

import play.api.libs.json._

sealed trait T
case class A(s: String) extends T

implicit val writesT: OWrites[T] = {
  val writesA: OWrites[A] = Json.writes[A]

  OWrites[T] {
    case t: A => writesA.writes(t) + ("_type" -> Json.toJson("A"))
    case _ => ???
  }
}

val x = A("str")

// Exiting paste mode, now interpreting.

import play.api.libs.json._
defined trait T
defined class A
writesT: play.api.libs.json.OWrites[T] = play.api.libs.json.OWrites$$anon$3@69a03da1
x: A = A(str)

scala> println(Json.toJson[T](x)(writesT))
{"s":"str","_type":"A"}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...