Rhys Bradbury Rhys Bradbury - 3 months ago 7x
JSON Question

Scala PlayJson Cyclic Reference


I have a case class which is an item in a hierarchy, which refers to itself like so:

case class Node(
name: String,
children: Option[Seq[Node]] = None

I would like a PlayJson
for this.

Usually, you can just do:

implicit lazy val formatter = Json.format[MyCaseClass]

But this doesn't work.


PlayJson uses a Scala macro to produce a
for the case class, it will go through all fields, when it gets to the field
it will look for an existing formatter for
which it hasn't constructed yet, ending with a compilation error:

No implicit format for Option[Seq[Node]] available.
[error] implicit lazy val formatter = Json.format[Node]


What's the best way to approach this?

Is this a known issue with PlayJson?


This is something that can be found under recursive types in play-json docs:

import play.api.libs.functional.syntax._
import play.api.libs.json.{Reads, Writes, _}

case class Node(name: String, children: Option[Seq[Node]] = None)

implicit lazy val nodeReads: Reads[Node] = (
  (__ \ "name").read[String] and
  (__ \ "children").lazyReadNullable(Reads.seq[Node](nodeReads))

implicit lazy val nodeWrites: Writes[Node] = (
  (__ \ "name").write[String] and
  (__ \ "children").lazyWriteNullable(Writes.seq[Node](nodeWrites))

Since in that case Reads and Writes are symmetrical, you can create the whole thing as a single Format:

implicit lazy val nodeFormat: Format[Node] = (
  (__ \ "name").format[String] and
  (__ \ "children").lazyFormatNullable(Reads.seq[Node](nodeFormat), Writes.seq[Node](nodeFormat))
)(Node.apply, unlift(Node.unapply))