2

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.

1
  • 1
    The very last line is not valid JSON, but without the outer braces {}, it is. I'm assuming that's what you meant. Commented Mar 2, 2017 at 20:27

3 Answers 3

7

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.

Sign up to request clarification or add additional context in comments.

3 Comments

Was this deprecated in 2.6, or is there an import statement one needs in the controller performing this method? On this line request.body.validate[Example].fold( IntelliJ underlines the validate part, saying it cannot resolve it.
@NateH06 see playframework.com/documentation/2.6.x/… for what was changed around this stuff in play 2.6.
Is there any "simple" way without having to create extra class? just put the result in List[Map[String, String]] perhaps?
2

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.

10 Comments

When I do it this way, IntelliJ complains about the request.body part, saying "Expression of type AnyContent doesn't conform to expected type Listminimal reproducible example"
you left off the parse.json portion. Action.async(parse.json[List[Example]]). The default body parse in Play will give you AnyContent.
also, try compiling outside of intellij (with sbt directly). Intellij has been known to get some things wrong from time to time.
No, didn't forget it and definitely copy and pasted your code, swapping out Example with my case class. I tried compiling with SBT on its own, and it gave a compile error with no message. I also tried compiling in the browser, and it gave me a bit more: type mismatch; found : play.api.mvc.AnyContent required: List[models.jsonmodels.Example]
"it gave a compile error with no message" This seems odd. Is it possible you're missing a Reads for your case class?
|
1

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")
    }
  }

2 Comments

This will return a 500 Internal Server Error if the body isn't json or doesn't match the Jon schema of Example.
The reason I want to parse the array manually is because I want to return 200 in all ways, just different content result accordingly. All the answers doesn't help at all, is it that difficult to parse a Json Array in play framework scala? I am new to scala and it seems very difficult as I can't find any solution to such simple problem after 5 minutes of Googling

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.