1
votes

Hi everyone recently I faced an issue in converting json into my own data model.

I have a json format message which may contain an empty string:

{
    "name" : "John Doe", 
    "hobbies": ""
}

or a list of hobby types:

{
    "name" : "John Doe", 
    "hobbies": [{"name":"basketball"}]
}

And the following is my case class data model in scala play framework:

case class Person(name: String, hobbies: List[Hobby])
case class Hobby(name: String)

Right now I'm using the default json formatter but of course it's not working well when we have empty string as value.

implicit val HobbyJson= Json.format[Hobby]
implicit val PersonJson = Json.format[Person]

it will throw exception if the hobbies has a empty string. I want to convert it into an empty list when it's the empty string. I search the document Play provides but couldn't find infomation. Can anyone give some suggestions?

Thanks in advance.

2

2 Answers

0
votes

As you mentioned, the default Format macros won't work for you here because of the inconsistent treatment of hobbies. So you need to implement your own Reads[Person] - here's how I'd do it:

object PersonJson {
  implicit val hobbyConverter = Json.format[Hobby]

  val personReads = new Reads[Person] {

    override def reads(json: JsValue): JsResult[Person] = {
      for {
        personName  <- (json \ "name").validate[String]
        hobbies     <- (json \ "hobbies").validate[JsValue]
      } yield {
        val maybeHobbyList = hobbies.validate[List[Hobby]].asOpt
        Person(personName, maybeHobbyList.getOrElse(Nil))
      }
    }
  }

  implicit val personConverter = Format(personReads, Json.writes[Person])
}

The key thing to note here is surrounding the whole thing in a JsResult courtesy of the for-comprehension and the yield. This gives us all the necessary checking (like the name field being there and being a String, and the hobbies field being there).

The code within the yield block only runs if we've got something that looks pretty close to a Person. Then we can safely try validating the hobbies as a List[Hobby], and convert the result to an Option[List[Hobby]]. It'll be a None if it didn't work (thus it must have been a string) and so we default it to the empty list as required.

0
votes

Thanks @millhouse answer, it definitely works. Like he said we need a custom Reads[Person] to properly convert it.

I also post my code as reference.

  implicit val personJsonReads: Reads[Person] = (
      (__ \ "name").read[String] and
      (__ \ "hobbies").read[List[Hobby]].orElse(Reads.pure(List())) 
  ) (Person.apply _)

read[List[Hobby]].orElse(Reads.pure(List())) will generate the empty list when the value cannot convert to List[Hobby].