I'm trying to map a Scala case class to JSON using Play 2.x. This works for simple versions of the case class, but not when there's a Seq or List of objects involved: then I get 'no implicit format' and 'no unapply function found' errors.
The code I'm using for this is the following:
case class Book(title: String, authors: Seq[Author])
case class Author(name: String)
I've used the Json.format
macro to generate the Reads and Writes for this:
implicit val bookFormat = Json.format[Book]
implicit val authorFormat = Json.format[Author]
But now when I'm compiling my code, I get the following error:
Error:(25, 40) Play 2 Compiler:
/Users/erikp/Userfiles/projects/play/booksearch/app/models/user.scala:25: No implicit format for Seq[models.Author] available.
implicit val bookFormat = Json.format[Book]
^
Without the Seq it works nicely, but with the Seq, it fails. I tried adding implicit val authorsFormat = Json.format[Seq[Author]]
to the implicit converters, but that has no effect.
Define the formatters respecting their dependency order, for each class in the graph that needs to be serialized.
Formatting Book
requires formatting Author
, so define the Author
formatter before the Book
formatter.
For example, with this Models.scala
file:
package models
import play.api.libs.json._
case class Book(title: String, authors: Seq[Author])
case class Author(name: String)
object Formatters {
implicit val authorFormat = Json.format[Author]
implicit val bookFormat = Json.format[Book]
}
and this JsonExample.scala
file:
package controllers
import models._
import models.Formatters._
import play.api.mvc._
import play.api.libs.json._
object JsonExample extends Controller {
def listBooks = Action {
val books = Seq(
Book("Book One", Seq(Author("Author One"))),
Book("Book Two", Seq(Author("Author One"), Author("Author Two")))
)
val json = Json.toJson(books)
Ok(json)
}
}
a request to listBooks
will produce this result:
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Content-Length: 133
<
[{"title":"Book One","authors":[{"name":"Author One"}]},{"title":"Book Two","authors":[{"name":"Author One"},{"name":"Author Two"}]}]
For more advanced formatting, including partial serialization to avoid having to declare formatters for classes that should not be serialized, see JSON Reads/Writes/Format Combinators.
It should be kept in mind that the classes to be serialized don't necessarily have to be the domain model classes. It may be helpful to declare data transfer object (DTO) classes that reflect the desired JSON structure, and instantiate them from the domain model. This way, serialization is straightforward with Json.format
and there isn't the issue of partial serialization, with the added benefit of a typesafe representation of the JSON API.
For example, this BookDTO.scala
file defines a BookDTO
data transfer object that uses only types that can be serialized to JSON without requiring further definition:
package dtos
import models._
import play.api.libs.json.Json
case class BookDTO (title: String, authors: Seq[String])
object BookDTO {
def fromBook(b: Book) = BookDTO(b.title, b.authors.map(_.name))
implicit val bookDTOFormat = Json.format[BookDTO]
}
and this JsonExample2.scala
file shows how to use this pattern:
package controllers
import dtos._
import dtos.BookDTO._
import models._
import play.api.mvc._
import play.api.libs.json._
import play.api.libs.functional.syntax._
object JsonExample2 extends Controller {
def listBooks = Action {
val books = Seq(
Book("Book One", Seq(Author("Author One"))),
Book("Book Two", Seq(Author("Author One"), Author("Author Two")))
)
val booksDTO = books.map(BookDTO.fromBook(_))
Ok(Json.toJson(booksDTO))
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With