scalaplayframeworkplay-json

Converting JsObjects in Scala: how to use optional fields?


I'm trying to create a scala function that converts a JsObject to one with a different structure.

All values are optional and so the function shouldn't throw an error if they don't appear on the input object.

I have the following code which works when I use .as[String] on the lookups rather than asOpt but I can't work out how to get this working with optional fields. I'm not sure how to convert from Option when creating the output object:

val input = Json.obj(
    "title" -> "a book",
    "author" -> Json.obj("name" -> "mr smith"),
    "metadata" -> Json.obj("published" -> 10983920189403L))

case class Book(title: Option[String], author: Option[String], published: Option[Long])

  def bookConvertor(inputObject: JsObject): JsObject = {
    Try {
      val title = (inputObject \ "title").asOpt[String]
      val author = (inputObject \ "author" \ "name").asOpt[String]
      val published = (inputObject \ "metadata" \ "published").asOpt[Long]
      Book(title, author, published)
    } match {
      case Success(book) => {
        JsObject(
          Seq(
            "title" -> JsString(book.title),
            "author" -> JsString(book.author),
            "published" -> JsString(book.published)
          )
        )
      }
    }
  }

Any help would be great. I am new to Scala.


Solution

  • To convert from a Book to a JsObject, you can use play-json macros rather than doing it manually:

    val writes: OWrites[Book] = Json.writes[Book]
    
    val book: Book = ???
    val jsObj: JsObject = Json.toJsObject(book)
    

    If you wanted to do it manually, you could do something like this for each attribute:

    ...
    "title" -> book.title.map(JsString(_)).getOrElse(JsNull)
    ...
    

    But don't, let the library do the boring painful part for you :)