Making Reads and Writes in Scala Play for lists of custom classes

12,236

Solution 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.

Solution 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.

Solution 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.

Share:
12,236

Related videos on Youtube

Will
Author by

Will

Updated on June 04, 2022

Comments

  • Will
    Will almost 2 years

    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.

  • Jonik
    Jonik over 8 years
    You are right in that Json.reads[List[Item]] and Json.writes[List[Item]] are not necessary; those cause the "No unapply or unapplySeq function found" error. However, List works just fine, no concrete type needed. I think OP's case can be solved by making the implicit converters properly visible, using one of the approaches here: stackoverflow.com/q/34615571/56285
  • Jonik
    Jonik over 8 years
    @Will: while this works, it is way more complicated than necessary, fortunately! As Ende pointed out, Play already knows how to deal with lists. Try this: stackoverflow.com/a/34618829/56285