Jon Snow
Jon Snow

Reputation: 23

Json Writes for custom class in scala

I have a Seq of some Strings mostly like below:

val states = Seq(
  "CA" -> Seq("Los Angeles" -> Seq("SunsetBlvd", "Hollywood" -> Seq("W 8th St", "W 9th St")), "Pasadena"),
  "WA" -> Seq("Seattle", "Redmond")
)

The case class for this could be

case class State(name: String, sub: Option[Seq[State]])

And an implicit Writes

implicit val stateWrites = Json.Writes[State]

Hoping to convert it to a Json like

[
  {
    "name": "CA",
    "sub": [
        {
         "name": "Los Angeles",
         "sub": [
         {
           "name": "SunsetBlvd"
          },
          {
            "name": "Hollywood",
            "sub": [
              {
                "name": "W 8th St"
              },
              {
                "name": "W 9th St"
              }
            ]
          }
        ]
      }
    ]
  },
  {
    "name": "WA",
    "sub": [
      {
        "name": "Seattle"
      },
      {
       "name": "Redmond"
      }
    ]
  }
]

How do I correctly model the data and be able to convert this Seq to a Json using Writes?

Or even change the states val to an appropriate format so I could easily convert it to Json?

In the case class, one of fields is of type itself which is wrong. How do I avoid that in modeling the data or even the Seq?

Upvotes: 1

Views: 802

Answers (2)

inafalcao
inafalcao

Reputation: 1452

That information has conceptually a tree structure. My advice is to threat it with just a normal case class, to simplify the json formatter and to have a much more semantic structure:

case class Tree(name: String, sub: Option[List[Tree]])

And your formatter would be just like this:

implicit val repositoryFormat: Format[Tree] = (
    (__ \ 'name).format[String] ~
      (__ \ 'sub).lazyFormatNullable(implicitly[ Format[ List[Tree] ]])
    )(Tree.apply, unlift(Tree.unapply))

Notice I used lazyFormatNullable to deal with the recursive reference to Tree in sub.

To simulate the Json you posted, I made the translation to the Tree case class structure.

// The tree leaves
  val hollywoodLeaves = Some( Tree("W 8th St", None) :: Tree("W 9th St", None) :: Nil )
  val losAngelesLeaves = Some( Tree("SunsetBlvd", None) :: Tree("Hollywood", hollywoodLeaves ) :: Nil  )

  // The two trees in your array
  val firstTree = Tree( "CA", Some( Tree("Los Angeles", losAngelesLeaves) :: Nil ) )
  val secondTree = Tree("WA", Some( Tree("Seattle", None) :: Tree("Redmond", None) :: Nil ))

  // Your root array
  val treeArray = firstTree :: secondTree :: Nil

  // Conversion to json
  val json = Json.toJson(treeArray)

Upvotes: 0

amorfis
amorfis

Reputation: 15770

I came up with something like this:

case class State(name: String, sub: Option[Seq[State]])

import play.api.libs.json._


implicit val optWrites = new Writes[Option[Seq[State]]] {
  override def writes(o: Option[Seq[State]]) = {
    if (o.isDefined) {
      Json.toJson(o.get)(stateSeqWrites)
    } else {
      JsNull
    }
  }
}

implicit val stateWrites = new Writes[State] {
  def writes(state: State) = {
    val l: Seq[(String, JsValueWrapper)] = Seq("name" -> JsString(state.name))
    val ll: Seq[(String, JsValueWrapper)] = if (state.sub.isDefined) {
      val subValue: JsValueWrapper = Json.toJson(state.sub)(optWrites)
      l :+ ("sub" -> subValue)
    } else {
      l
    }

    Json.obj(ll : _*)
  }
}

implicit val stateSeqWrites: Writes[Seq[State]] = new Writes[Seq[State]] {
  override def writes(s: Seq[State]) = {
    JsArray(s.map(Json.toJson(_)(stateWrites)))
  }
}

val states = Seq(
  State("CA", Some(Seq(State("Los Angeles", Some(Seq(State("SunsetBlvd", None), State("Hollywood", Some(Seq(State("W 8th St", None), State("W 9th St", None)))), State("Pasadena", None))))))),
  State("WA", Some(Seq(State("Seattle", None), State("Redmond", None))))
)

val json = Json.toJson(states)

println(json.toString())

Probably might get simplified, but it's late night here ;) It does what you need :)

Upvotes: 0

Related Questions