简体   繁体   中英

Play framework, parse json array from request

How could I parse an array of json objects to scala List or Array ?

For now I have a code that parses the single object:

{"id":1,"name":"example1"}

And this is the code:

def exampleAction = Action.async(parse.json) { implicit request =>
    for {
        id <- (request.body \ "id").asOpt[Int]
        name <- (request.body \ "name").asOpt[String]
    } yield {
        (exampleService.create(Example(id, name)) map { n => Created("Id of Object Added : " + n) }).recoverWith {
            case e => Future {
                InternalServerError("There was an error at the server")
            }
        }
    }.getOrElse(Future { BadRequest("Wrong json format") })
}

But how should I change it to parse json requests like this:

{[{"id":1,"name":"example1"},{"id":2,"name":"example2"}]}

I guess function map should be used somewhere.

Your controller shouldn't need to worry about validating and mapping specific class fields, that's the model's job. Assuming the Example case class you appear to be using, you can easily create a Reads[Example] using the Json.reads macro provided by Play. The implicit Reads should be placed in the companion object of the corresponding class, so the implicit can be picked up from anywhere. You can also create more custom Reads if you need to by reading through the documentation , but for now we'll stick to the basics.

import play.api.libs.json._

case class Example(id: Int, name: String)

object Example {
  implicit val reads: Reads[Example] = Json.reads[Example]
}

Then, in your controller, you can use JsValue#validate[A] to attempt to de-serialize the entire model at once. Doing so returns a JsResult[A] , which can either be a JsSuccess[A] that holds the de-serialized model, or a JsError . We can fold the result to handle both cases.

def exampleAction = Action.async(parse.json) { implicit request =>
  request.body.validate[Example].fold(
    error => Future.successful(InternalServerError("JSON did not validate.")),
    example => {
      // Do something with the case class
      exampleService.create(example).map { 
        // ...
      } recoverWith {
        // ...
      }
    }
  )
}

Now, you can easily change the above controller to handle a array instead of a single model by changing:

request.body.validate[List[Example]]

And the second part of the fold method you will get a List[Example] that you can work with.


Note that in your error cases, instead of using Future { ... } to wrap what are essentially constant values that do not block anything, you can instead wrap them in Future.successful(...) to avoid dispatching trivial work out to the ExecutionContext .

Borrowing a little from Michael's answer we can simplify the controller code further by using a version of parse.json that is parameterized in its type .

Assuming the Reads exists:

import play.api.libs.json._

case class Example(id: Int, name: String)

object Example {
  implicit val reads: Reads[Example] = Json.reads[Example]
}

To handle a json array of Example objects you can simply use parse.json[List[Example]] as your body parser and then request.body will be a List[Example] :

def exampleAction = Action.async(parse.json[List[Example]]) { implicit request =>
  val examples: List[Example] = request.body

  // ...
}

Play will automatically return a 400 Bad Request if the body posted to this endpoint is not a valid json array of example objects.

I tried following the accepted answer and greggz' to the letter but couldn't get them to compile.

I'm building an answer off of gregghz' answer though. So... you'd definitely need to make the case class and companion object as he did:

import play.api.libs.json._

case class Example(id: Int, name: String)

object Example {
  implicit val reads: Reads[Example] = Json.reads[Example]
}

But your controller method can be even simpler, especially if you don't want to mess around with the Async. This worked for me because I was sending an HTML form from the client end, taking each input and sending ONLY that back to this method. There may be shortcomings to this way, and I'd appreciate hearing about them in the comments, but all you have to do from there is:

def exampleAction = Action {
    implicit request: Request[AnyContent] => {
      val jsonBody = request.body.asJson
      val myContent : List[Example] = jsonBody.get.as[List[Example]]
      // ... do rest of work here
      Ok("Json parsed")
    }
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM