Making Reads and Writes in Scala Play for lists of

2020-04-08 14:10发布

问题:

So I have two classes in my project

case class Item(id: Int, name: String)

and

case class Order(id: Int, items: List[Item])

I'm trying to make reads and writes properties for Order but I get a compiler error saying:

"No unapply or unapplySeq function found"

In my controller I have the following:

implicit val itemReads = Json.reads[Item]
implicit val itemWrites = Json.writes[Item]
implicit val listItemReads = Json.reads[List[Item]]
implicit val listItemWrites = Json.writes[List[Item]]

The code works for itemReads and itemWrites but not for the bottom two. Can anyone tell me where I'm going wrong, I'm new to Play framework.

Thank you for your time.

回答1:

The "No unapply or unapplySeq function found" error is caused by these two:

implicit val listItemReads = Json.reads[List[Item]]
implicit val listItemWrites = Json.writes[List[Item]]

Just throw them away. As Ende said, Play knows how to deal with lists.

But you need Reads and Writes for Order too! And since you do both reading and writing, it's simplest to define a Format, a mix of the Reads and Writes traits. This should work:

case class Item(id: Int, name: String)

object Item {
  implicit val format = Json.format[Item]
}

case class Order(id: Int, items: List[Item])

object Order {
  implicit val format = Json.format[Order]
}

Above, the ordering is significant; Item and the companion object must come before Order.

So, once you have all the implicit converters needed, the key is to make them properly visible in the controllers. The above is one solution, but there are other ways, as I learned after trying to do something similar.



回答2:

You don't actually need to define those two implicits, play already knows how to deal with a list:

scala> import play.api.libs.json._ 
import play.api.libs.json._

scala>   case class Item(id: Int, name: String)
defined class Item

scala>   case class Order(id: Int, items: List[Item])
defined class Order

scala>   implicit val itemReads = Json.reads[Item]
itemReads: play.api.libs.json.Reads[Item] = play.api.libs.json.Reads$$anon$8@478fdbc9

scala>   implicit val itemWrites = Json.writes[Item]
itemWrites: play.api.libs.json.OWrites[Item] = play.api.libs.json.OWrites$$anon$2@26de09b8

scala>   Json.toJson(List(Item(1, ""), Item(2, "")))
res0: play.api.libs.json.JsValue = [{"id":1,"name":""},{"id":2,"name":""}]

scala>   Json.toJson(Order(10, List(Item(1, ""), Item(2, ""))))
res1: play.api.libs.json.JsValue = {"id":10,"items":[{"id":1,"name":""},{"id":2,"name":""}]}

The error you see probably happens because play uses the unapply method to construct the macro expansion for your read/write and List is an abstract class, play-json needs concrete type to make the macro work.



回答3:

This works:

case class Item(id: Int, name: String)

case class Order(id: Int, items: List[Item])

implicit val itemFormat = Json.format[Item]
implicit val orderFormat: Format[Order] = (
  (JsPath \ "id").format[Int] and
    (JsPath \ "items").format[JsArray].inmap(
      (v: JsArray) => v.value.map(v => v.as[Item]).toList,
      (l: List[Item]) => JsArray(l.map(item => Json.toJson(item)))
    )
  )(Order.apply, unlift(Order.unapply))

This also allows you to customize the naming for your JSON object. Below is an example of the serialization in action.

Json.toJson(Order(1, List(Item(2, "Item 2"))))
res0: play.api.libs.json.JsValue = {"id":1,"items":[{"id":2,"name":"Item 2"}]}

Json.parse(
  """
    |{"id":1,"items":[{"id":2,"name":"Item 2"}]}
  """.stripMargin).as[Order]
res1: Order = Order(1,List(Item(2,Item 2)))

I'd also recommend using format instead of read and write if you are doing symmetrical serialization / deserialization.