Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 format macro?

like image 611
Rhys Bradbury Avatar asked Jun 23 '16 10:06

Rhys Bradbury


1 Answers

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))
like image 54
Paweł Jurczenko Avatar answered Oct 22 '22 19:10

Paweł Jurczenko