Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 589
Andrei Yusupau Avatar asked Jun 11 '26 20:06

Andrei Yusupau


2 Answers

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.

like image 116
Michael Zajac Avatar answered Jun 13 '26 17:06

Michael Zajac


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.

like image 27
gregghz Avatar answered Jun 13 '26 17:06

gregghz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!