Json implicit format with recursive class definition

2.3k views Asked by At

I have a recursive class defined :

case class SettingsRepository(id: Option[BSONObjectID],
                          name: Option[String],
                          children: Option[List[SettingsRepository]])

with a JSON implicit format as below :

implicit val repositoryFormat = Json.format[SettingsRepository]

How can I do to resolve this compilation error? :

No implicit format for Option[List[models.practice.SettingsRepository]] available.
In /path/to/the/file.scala:95

95 implicit val repositoryFormat = Json.format[SettingsRepository] 

I tried to define a lazy reads/write/format wrapper without any success... Anyone know a clean way to do that?

3

There are 3 answers

0
Travis Brown On BEST ANSWER

As you've discovered, you can't use the JSON inception macro here, but you can write your own Format (note that I've replaced BSONObjectID with Long for the sake of a complete working example):

import play.api.libs.functional.syntax._
import play.api.libs.json._

case class SettingsRepository(
  id: Option[Long],
  name: Option[String],
  children: Option[List[SettingsRepository]]
)

implicit val repositoryFormat: Format[SettingsRepository] = (
  (__ \ 'id).formatNullable[Long] and
  (__ \ 'name).formatNullable[String] and
  (__ \ 'children).lazyFormatNullable(implicitly[Format[List[SettingsRepository]]])
)(SettingsRepository.apply, unlift(SettingsRepository.unapply))

The trick is providing an explicit type annotation and using implicitly rather than just a type parameter on lazyFormatNullable.

0
Tomer Shetah On

Just an update. This is already fixed for a long time. For example, in play-json version 2.3.9, which is the oldest available at Maven, the following works:

import play.api.libs.json._

case class SettingsRepository(id: Option[Int],
                              name: Option[String],
                              children: Option[List[SettingsRepository]])

implicit val repositoryFormat = Json.format[SettingsRepository]

val s = SettingsRepository(Some(2), Some("name"), Some(List(SettingsRepository(Some(1), Some(""), None))))
println(Json.toJsObject(s))

and outputs:

{"id":2,"name":"name","children":[{"id":1,"name":""}]}

Code run at Scastie.

0
bbarker On

For others who came here looking for a slight variant where we override reads and writes in the Format class (like the example given on the API docs), you can declare a lazy reference to your needed object:

  lazy val tweetFormat: Format[Tweet] = TweetFormat
  implicit object UserFormat extends Format[User] {
  //...
  }

  //...
  implicit object TweetFormat extends Format[Tweet] {
  //...