Search code examples
jsonscalaplayframework-2.0playframework-2.2

Play Framework 2.2.1 / How should controller handle a queryString in JSON format


My client side executes a server call encompassing data (queryString) in a JSON object like this:

?q={"title":"Hello"}   //non encoded for the sample but using JSON.stringify actually

What is an efficient way to retrieve the title and Hello String?

I tried this:

val params = request.queryString.map {case(k,v) => k->v.headOption}

that returns the Tuple: (q,Some({"title":"hello"}))

I could further extract to retrieve the values (although I would need to manually map the JSON object to a Scala object), but I wonder whether there is an easier and shorter way.

Any idea?


Solution

  • First, if you intend to pluck only the q parameter from a request and don't intend to do so via a route you could simply grab it directly:

    val q: Option[String] = request.getQueryString("q")
    

    Next you'd have to parse it as a JSON Object:

    import play.api.libs.json._
    val jsonObject: Option[JsValue] = q.map(raw: String => json.parse(raw))
    

    with that you should be able to check for the components the jsonObject contains:

    val title: Option[String] = jsonObject.flatMap { json: JsValue =>
      (json \ "title").asOpt[String]
    }
    

    In short, omitting the types you could use a for comprehension for the whole thing like so:

    val title = for {
      q <- request.getQueryString("q")
      json <- Try(Json.parse(q)).toOption
      titleValue <- (json \ "title").asOpt[String]
    } yield titleValue
    

    Try is defined in scala.util and basically catches Exceptions and wraps it in a processable form.

    I must admit that the last version simply ignores Exceptions during the parsing of the raw JSON String and treats them equally to "no title query has been set". That's not the best way to know what actually went wrong.

    In our productive code we're using implicit shortcuts that wraps a None and JsError as a Failure:

    val title: Try[String] = for {
      q <- request.getQueryString("q") orFailWithMessage "Query not defined"
      json <- Try(Json.parse(q))
      titleValue <- (json \ "title").validate[String].asTry
    } yield titleValue
    

    Staying in the Try monad we gain information about where it went wrong and can provide that to the User.

    orFailWithMessage is basically an implicit wrapper for an Option that will transform it into Succcess or Failure with the specified message.

    JsResult.asTry is also simply a pimped JsResult that will be Success or Failure as well.