Reputation: 633
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.
Upvotes: 2
Views: 4953
Reputation: 3574
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")
}
}
Upvotes: 1
Reputation: 3965
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.
Upvotes: 2
Reputation: 55569
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
.
Upvotes: 7