简体   繁体   中英

How to define format for both Y and List[X] in Play JSON when X extends from Y and Y is trait?

I can't convert list in specific case: when type is extends from trait.

When I can convert:

  import play.api.libs.functional.syntax._
  import play.api.libs.json._
  sealed trait Item
  case class Id(id: Long) extends Item
  case class MyList(list: List[Id])
  object MyFormat {
    implicit lazy val idFormat = Json.format[Id]
    implicit lazy val myListFormat = Json.format[MyList]
  }

When I can not convert:

  sealed trait Item
  case class Id(id: Long) extends Item
  case class MyList(list: List[Id])
  object MyFormat {
    implicit lazy val itemFormat = new Format[Item] {
      override def writes(o: Item): JsValue = o match {
        case i: Id => idFormat.writes(i)
      }
      override def reads(json: JsValue): JsResult[Item] = {
        idFormat.reads(json)
      }
    }
    implicit lazy val idFormat = Json.format[Id]
    implicit lazy val myListFormat = Json.format[MyList]
  }

Error: Error:(33, 49) No instance of play.api.libs.json.Format is available for scala.collection.immutable.List[Main2.Id] in the implicit scope (Hint: if declared in the same file, make sure it's declared before) implicit lazy val myListFormat = Json.format[MyList]

Why I can't format in 2nd case?

If I add formatter for list:

implicit lazy val idsFormat = Json.format[List[Id]]

then I got Error:(33, 46) No instance of Reads is available for scala.collection.immutable.Nil in the implicit scope (Hint: if declared in the same file, make sure it's declared before) implicit lazy val idsFormat = Json.format[List[Id]]

PS: The only one solution than I found:

  1. Define custom format for List[Id]
  2. When read or write, use format for Id
  3. When read, use
def flatten[T](xs: Seq[JsResult[T]]): JsResult[List[T]] = {
  val (ss: Seq[JsSuccess[T]], fs: Seq[JsError]) = xs.partition(_.isSuccess)
  if (fs.isEmpty) JsSuccess(ss.map(_.get).toList) else fs.head
}

If play JSON features automatic/semiautomatic codec instances derivation, then it will use implicit to enable such a mechanism. It means that codecs for complex things should be introduced after their components.

In your case, it seems like play JSON tries to derive codec for List as for case class ,ie as for List(a1, List(a2, ..., List(an, Nil))) and when it hits Nil, it doesn't know how to derive codec for that.

I believe you want your list encoded not like a chain of folded objects but as JSON array.

Then you should search play sources for default List[T] codec and try to use it by specializing it for Id .

General tool for debugging missing implicits is compiler option "-Xlog-implicits". It will log all failed implicit searches to console, and it is possible to figure out what's missing by these messages.

It is also strongly advised to know how implicit works before working with libraries that use this feautre extensively.

And last, but not least:

Have you ever tried using circe ? It features automatic and semi-automatic JSON derivation for families of sealed traits and standard scala classes. It even has integration with play framework. Circe derivation removes the most headache of writing codec code, but requires strong knowledge of implicit precedence to work properly. The way it works described here and here .

You can also try to create your own derivation for play-json if there's no adequate standard one with morphling .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM