Search code examples
jsonscalarecursionplayframeworkplayframework-json

Create recursive JSON Writes without combinator pattern


When using JSON combinators, one can create a recursive structure using lazyWrite as stated in the documentation:

implicit lazy val userWrites: Writes[User] = (
  (__ \ "name").write[String] and
  (__ \ "friends").lazyWrite(Writes.seq[User](userWrites))
)(unlift(User.unapply))

Is it possible to do that while implementing writes, ie:

implicit lazy val userWrites: Writes[User] = new Writes[User]{
    def writes(user: User) = Json.obj(
        "name" -> user.name,
        "friends" -> ??????
    )
}

Solution

  • Yes. In fact, quite easy.

    implicit lazy val userWrites: Writes[User] = new Writes[User] {
        def writes(user: User) = Json.obj(
            "name" -> user.name,
            "friends" -> user.friends
        )
    }
    
    
    val joe = User("Joe", Nil)
    val bob = User("Bob", Nil)
    val jane = User("Jane", Seq(bob, joe))
    val james = User("James", Seq(bob, jane))
    
    scala> Json.toJson(james)
    res0: play.api.libs.json.JsValue = {"name":"James","friends":[{"name":"Bob","friends":[]},{"name":"Jane","friends":[{"name":"Bob","friends":[]},{"name":"Joe","friends":[]}]}]}
    

    userWrites doesn't really need to be lazy either. It works just fine like this because the combinators try to generate the writes by resolving child writes implicitly and working it's way down the tree, which is why it needs the lazyWrite to stop it from descending infinitely in a recursive structure. When writing def writes, we're explicitly stating what the writes are.

    Neither, however, can save you from this scenario:

    def jim: User = User("Joe", Seq(dwight))
    def dwight: User = User("Bob", Seq(jim))