0

POST ing json from javascript to server in Play Framework:

var myJson = {"name": "joe", "age":20};
var obj = JSON.parse(myJson);

$.ajax(jsRoutes.controllers.MyController.create(obj));

Now, I have the javascript router configured fine. If i recieve the obj as a string I can print it out to the console just fine.

routes.conf:

POST    /person/add     controllers.MyController.createFromAjax(ajax: String)

BUT, I want to write the json to MongoDB using an Async promise which Activator gives the compile time error:

scala.concurrent.Future[play.api.mvc.Result][error]  cannot be applied to (String)

I have other routes that take no parameters that receive json using Postman and write it to MongoDB just fine

routes.conf

POST    /heartrates/bulk            controllers.HRController.createFromJson 

If I omit the parameter on the route that receives the json from Ajax instead of using Postman I get a HTTP 400 error in the browser.

POST http://localhost:9000/person/add 400 (Bad Request)

SO, my question is, Ajax needs a parameter but String wont work. Play documentation says json is always received as a String. What am I doing wrong here?

Scala Controller Code taken from Lightbend seed Play.Reactive.MongoDB:

def createBulkFromAjax = Action.async(parse.json) { request =>

val documents = for {
  heartRate        <- request.body.asOpt[JsArray].toStream
  maybeHeartRate   <- heartRate.value
  validHeartRate   <- maybeHeartRate.transform(transformer).asOpt.toList
} yield validHeartRate

for {
  heartRate <- hrFuture
  multiResult <- heartRate.bulkInsert(documents = documents, ordered = true)
} yield {
  Logger.debug(s"Successfully inserted with multiResult: $multiResult")
  Created(s"Created ${multiResult.n} heartRate")
}
}
cchantep
  • 9,118
  • 3
  • 30
  • 41
Eoghan Hynes
  • 41
  • 11

2 Answers2

0

I think you're getting mixed up between the parameters you pass to your Action as part of the jsRoutes call, and parameters that get passed to endpoints (i.e. the query string, query parameters etc).

Play will return a 400 Bad Request if you've declared a non-optional parameter (like you did with ajax: String) and you don't then actually supply it in your request.

While conceptually you are passing obj to your action, it's not as a query parameter - you've declared that your endpoint expects an HTTP POST - so the JSON should be in the HTTP request body. Notice your other endpoints don't take any query parameters.

So step 1 is to fix your routes file (I've renamed your method to match your other existing working one):

POST    /person/add     controllers.MyController.createFromJson

If you look at the Play documentation for the Javascript reverse router, you'll see that you'll need to set the type (aka HTTP method) if you're doing something other than a GET. So, step 2, here's what your Javascript should look like to achieve a POST:

var myJson = {"name": "joe", "age":20};
var obj = JSON.stringify(myJson);

var r = controllers.MyController.createFromJson;
$.ajax({url: r.url, type: r.type, data: obj });

After those changes you should be good; your controller code looks fine. If you still get 400 Bad Request responses, check that jQuery is setting your Content-Type header correctly - you may need to use the contentType option in the jQuery $.ajax call.

Edit after still getting 400 errors:

I've just noticed that you were using JSON.parse in your Javascript - as per this answer you should be using JSON.stringify to convert an object into something jQuery can send - otherwise it may try to URLEncode the data and/or send the fields as query parameters.

The other thing to look at is whether the JSON you are sending actually agrees with what you're trying to parse it as. I'm not sure if you've provided a simplified version for this question but it looks like you're trying to parse:

{"name": "joe", "age":20}

Using:

request.body.asOpt[JsArray]

Which will always result in a None - you didn't give it an array.

Community
  • 1
  • 1
millhouse
  • 9,817
  • 4
  • 32
  • 40
  • I set up jsrouter as per Play documentation so the only route that javascript doesnt complain about is: var r = jsRoutes.controllers.MyController.createFromJson(); $.ajax({url: r.url, contentType: "application/json", type: r.type, data: JsonObj }); I have added contentType param to ajax as above but still get error 400 the jsrouter seems to be working fine producing: POST http://localhost:9000/person/add 400 (Bad Request) Any further help appreciated. Thanks. – Eoghan Hynes Feb 21 '17 at 18:18
  • Hey Hey, It works. I WAS simplifying my code for this question but it turns out my problem was a duplicated route in route.conf from when i was passing json as a parameter. Deleted the route and it works with or without Json.parse because I had hard coded in the square brackets. I didnt know about stringify. Thanks for you help @millhouse. Im sure I will have more questions to come. – Eoghan Hynes Feb 22 '17 at 09:39
  • correction, you were right about json.parse. I've deleted it. it works. – Eoghan Hynes Feb 22 '17 at 09:48
0

The Answer to ajax javascript routes in Play Framework 2.5 for ReativeMongo:

routes.conf:

GET     /javascriptRoutes      controllers.HRController.javascriptRoutes

HRController:

def javascriptRoutes = Action { implicit request =>
Ok(
  JavaScriptReverseRouter("jsRoutes")(
    routes.javascript.HRController.createBulkFromAjax
  )
).as("text/javascript")

}

routes.conf:

POST    /heartrates/add     controllers.HRController.createBulkFromAjax

main.scala.html:

<script type="text/javascript" src="@routes.HRController.javascriptRoutes"></script>

javascript:

var r = jsRoutes.controllers.HRController.createBulkFromAjax();
$.ajax({url: r.url, type: r.type, contentType: "application/json", data: JsonString });

HRController:

def createBulkFromAjax = Action.async(parse.json) { request =>
//Transformation silent in case of failures.
val documents = for {
  heartRate        <- request.body.asOpt[JsArray].toStream
  maybeHeartRate   <- heartRate.value
  validHeartRate   <- maybeHeartRate.transform(transformer).asOpt.toList
} yield validHeartRate

for {
  heartRate <- hrFuture
  multiResult <- heartRate.bulkInsert(documents = documents, ordered = true)
} yield {
  Logger.debug(s"Successfully inserted with multiResult: $multiResult")
  Created(s"Created ${multiResult.n} heartRate")
}
}

HRController.createBulkFromAjax was built from a Lightbend activator ui seed example called play.ReactiveMogno

Eoghan Hynes
  • 41
  • 11