Rhys Bradbury Rhys Bradbury - 5 months ago 16
JSON Question

Scala PlayJson Cyclic Reference

Context

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
Format
for this.

Usually, you can just do:

implicit lazy val formatter = Json.format[MyCaseClass]


But this doesn't work.

Why?

PlayJson uses a Scala macro to produce a
Format
for the case class, it will go through all fields, when it gets to the field
children
it will look for an existing formatter for
Node
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]





Questions

What's the best way to approach this?

Is this a known issue with PlayJson?

Answer

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))
)(Node)

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

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