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