problems with handeling Json request in play - json

I'm trying to make server which can handle HTTP requests with Json. Sorry if I haver. I'm just still new to all this.
So, I made function which takes JsValue and working with it.
def find(info: JsValue) = {
val req = Search.makeRequest("person",info)
val result = DB.withConnection { implicit c =>
SQL(req).as(person *)
}
Json.toJson(result)
}
Then I do something like this:
val test = Json.parse("""{"name":"John"}""")
Person.find(test)
It works fine. But then I try to call this function with HTTP request:
routes file:
GET /findperson controllers.PersonController.findPerson(info: String)
controller:
def findPerson(info: String) = Action {
Ok(Person.find(Json.parse(info)))
}
actual request:
http://localhost:9000/findperson?info="""{"name":"John"}"""
I get:
Exception: Malformed JSON: Got a sequence of JsValue outside an array or an object.
Can someone tell me how do it right? Please.

Although I agree with #Ryan that it is an unusual thing to want to do, I think the problem you are having is actually as the message says, "Malformed JSON". Remember that your url parameter is not source code, it's a simple string. There's no need to escape the quotation marks. So try using the url:
http://localhost:9000/findperson?info={"name":"John"}

Related

Kotlin unable to request JSON files with more than one directory in URL?

While trying to download and parse JSON files through Kotlin, it kept failing trying to access the document, though trying different (shorter) URLS seemed to work fine
val string = "http://ddragon.leagueoflegends.com/cdn/11.2.1/data/en_GB/champion.json"
val client = OkHttpClient()
val request = Request.Builder().url(string).build()
client.newCall(request).enqueue(object: Callback
{
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
println(body)
}
override fun onFailure(call: Call, e: IOException) {
println("Failed")
}
})
This gives the "Failed" output in the console:failed#1
However, using a shorter URL such as this:
val string = "https://ddragon.leagueoflegends.com/api/versions.json"
Gives the correct output: working #1
Anyone know why and/or a fix to this?
Thanks!
Update:
Trying with a file that is considerably smaller than the first, but includes two directories instead of one:
val string = "http://static.developer.riotgames.com/docs/lol/maps.json"
Still ends up failing leading me to believe it is unable to access the document if it is too nested within directories?

Grails: Easy and efficient way to parse JSON from a Request

Please pardon me if this is a repeat question. I have been through some of the questions/answers with a similar requirement but somehow got a bit overwhelmed and confused at the same time. My requirement is:
I get a JSON string/object as a request parameter. ( eg: params.timesheetJSON )
I then have to parse/iterate through it.
Here is the JSON that my grails controller will be receiving:
{
"loginName":"user1",
"timesheetList":
[
{
"periodBegin":"2014/10/12",
"periodEnd":"2014/10/18",
"timesheetRows":[
{
"task":"Cleaning",
"description":"cleaning description",
"paycode":"payCode1"
},
{
"task":"painting",
"activityDescription":"painting description",
"paycode":"payCode2"
}
]
}
],
"overallStatus":"SUCCESS"
}
Questions:
How can I retrieve the whole JSON string from the request? Does request.JSON be fine here? If so, will request.JSON.timesheetJSON yield me the actual JSON that I want as a JSONObject?
What is the best way to parse through the JSON object that I got from the request? Is it grails.converters.JSON? Or is there any other easy way of parsing through? Like some API which will return the JSON as a collection of objects by automatically taking care of parsing. Or is programatically parsing through the JSON object the only way?
Like I said, please pardon me if the question is sounding vague. Any good references JSON parsing with grails might also be helpful here.
Edit: There's a change in the way I get the JSON string now. I get the JSON string as a request paramter.
String saveJSON // This holds the above JSON string.
def jsonObject = grails.converters.JSON.parse(saveJSON) // No problem here. Returns a JSONObject. I checked the class type.
def jsonArray = jsonArray.timesheetList // No problem here. Returns a JSONArray. I checked the class type.
println "*** Size of jsonArray1: " + jsonArray1.size() // Returns size 1. It seemed fine as the above JSON string had only one timesheet in timesheetList
def object1 = jsonArray[1] // This throws the JSONException, JSONArray[1] not found. I tried jsonArray.getJSONObject(1) and that throws the same exception.
Basically, I am looking to seamlessly iterate through the JSON string now.
I have wrote some code that explains how this can be done, that you can see below, but to be clear, first the answers to your questions:
Your JSON String as you wrote above will be the contents of your POST payload to the rest controller. Grails will use its data binding mechanism to bind the incomming data to a Command object that your should prepare. It has to have fields corresponding to the parameters in your JSON String (see below). After you bind your command object to your actual domain object, you can get all the data you want, by simply operating on fields and lists
The way to parse thru the JSON object is shown in my example below. The incomming request is esentially a nested map, with can be simply accessed with a dot
Now some code that illustrates how to do it.
In your controller create a method that accepts "YourCommand" object as input parameter:
def yourRestServiceMethod (YourCommand comm){
YourClass yourClass = new YourClass()
comm.bindTo(yourClass)
// do something with yourClass
// println yourClass.timeSheetList
}
The command looks like this:
class YourCommand {
String loginName
List<Map> timesheetList = []
String overallStatus
void bindTo(YourClass yourClass){
yourClass.loginName=loginName
yourClass.overallStatus=overallStatus
timesheetList.each { sheet ->
TimeSheet timeSheet = new TimeSheet()
timeSheet.periodBegin = sheet.periodBegin
timeSheet.periodEnd = sheet.periodEnd
sheet.timesheetRows.each { row ->
TimeSheetRow timeSheetRow = new TimeSheetRow()
timeSheetRow.task = row.task
timeSheetRow.description = row.description
timeSheetRow.paycode = row.paycode
timeSheet.timesheetRows.add(timeSheetRow)
}
yourClass.timeSheetList.add(timeSheet)
}
}
}
Its "bindTo" method is the key piece of logic that understands how to get parameters from the incomming request and map it to a regular object. That object is of type "YourClass" and it looks like this:
class YourClass {
String loginName
Collection<TimeSheet> timeSheetList = []
String overallStatus
}
all other classes that are part of that class:
class TimeSheet {
String periodBegin
String periodEnd
Collection<TimeSheetRow> timesheetRows = []
}
and the last one:
class TimeSheetRow {
String task
String description
String paycode
}
Hope this example is clear enough for you and answers your question
Edit: Extending the answer according to the new requirements
Looking at your new code, I see that you probably did some typos when writting that post
def jsonArray = jsonArray.timesheetList
should be:
def jsonArray = jsonObject.timesheetList
but you obviously have it properly in your code since otherwise it would not work, then the same with that line with "println":
jsonArray1.size()
shuold be:
jsonArray.size()
and the essential fix:
def object1 = jsonArray[1]
shuold be
def object1 = jsonArray[0]
your array is of size==1, the indexing starts with 0. // Can it be that easy? ;)
Then "object1" is again a JSONObject, so you can access the fields with a "." or as a map, for example like this:
object1.get('periodEnd')
I see your example contains errors, which lead you to implement more complex JSON parsing solutions.
I rewrite your sample to the working version. (At least now for Grails 3.x)
String saveJSON // This holds the above JSON string.
def jsonObject = grails.converters.JSON.parse(saveJSON)
println jsonObject.timesheetList // output timesheetList structure
println jsonObject.timesheetList[0].timesheetRows[1] // output second element of timesheetRows array: [paycode:payCode2, task:painting, activityDescription:painting description]

Play Framework 2.2.1 / How should controller handle a queryString in JSON format

My client side executes a server call encompassing data (queryString) in a JSON object like this:
?q={"title":"Hello"} //non encoded for the sample but using JSON.stringify actually
What is an efficient way to retrieve the title and Hello String?
I tried this:
val params = request.queryString.map {case(k,v) => k->v.headOption}
that returns the Tuple: (q,Some({"title":"hello"}))
I could further extract to retrieve the values (although I would need to manually map the JSON object to a Scala object), but I wonder whether there is an easier and shorter way.
Any idea?
First, if you intend to pluck only the q parameter from a request and don't intend to do so via a route you could simply grab it directly:
val q: Option[String] = request.getQueryString("q")
Next you'd have to parse it as a JSON Object:
import play.api.libs.json._
val jsonObject: Option[JsValue] = q.map(raw: String => json.parse(raw))
with that you should be able to check for the components the jsonObject contains:
val title: Option[String] = jsonObject.flatMap { json: JsValue =>
(json \ "title").asOpt[String]
}
In short, omitting the types you could use a for comprehension for the whole thing like so:
val title = for {
q <- request.getQueryString("q")
json <- Try(Json.parse(q)).toOption
titleValue <- (json \ "title").asOpt[String]
} yield titleValue
Try is defined in scala.util and basically catches Exceptions and wraps it in a processable form.
I must admit that the last version simply ignores Exceptions during the parsing of the raw JSON String and treats them equally to "no title query has been set".
That's not the best way to know what actually went wrong.
In our productive code we're using implicit shortcuts that wraps a None and JsError as a Failure:
val title: Try[String] = for {
q <- request.getQueryString("q") orFailWithMessage "Query not defined"
json <- Try(Json.parse(q))
titleValue <- (json \ "title").validate[String].asTry
} yield titleValue
Staying in the Try monad we gain information about where it went wrong and can provide that to the User.
orFailWithMessage is basically an implicit wrapper for an Option that will transform it into Succcess or Failure with the specified message.
JsResult.asTry is also simply a pimped JsResult that will be Success or Failure as well.

Play Framework & Scala: Problems with FakeRequest & JSON POST in unit test

I have spent too much time trying to debug the following issue but I am not sure where the problem is occurring.
Issue: Getting 400 Bad Request, Invalid Json as response with following exception:
com.fasterxml.jackson.databind.JsonMappingException: No content to map due to end-of-input
at [Source: [B#6ee503c9; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164) ~[jackson-databind.jar:2.2.2]
at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:2931) ~[jackson-databind.jar:2.2.2]
at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:2846) ~[jackson-databind.jar:2.2.2]
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:1569) ~[jackson-databind.jar:2.2.2]
at play.api.libs.json.JacksonJson$.parseJsValue(JsValue.scala:480) ~[play-json_2.10.jar:2.2.0]
at play.api.libs.json.Json$.parse(Json.scala:27) ~[play-json_2.10.jar:2.2.0]
Method to be tested in my controller:
def createArticle(id: String) =
Action.async(parse.json) { implicit request =>
(request.body \ "content").asOpt[String].map {
............
............
}.getOrElse(BadRequest("Invalid request body"))
}
Corresponding Unit Test:
"create article" in {
running(FakeApplication()) {
val postJson = Json.obj("content" -> "article content")
val result = resource.createArticle(ARTICE_ID)(FakeRequest(POST, controllers.routes.ArticleResource.create(ARTICLE_ID).url).withJsonBody(postJson).withHeaders(CONTENT_TYPE -> "application/json").run
status(result) must equalTo OK
}
}
I read the discussion here but none of the suggestions there helped.
I had similar problems, but didn't solve them so far (elegantly, anyway..). As my environment was java instead of scala, I can just give a hunch. I think that it's possible that when you send post it's done asynchronously (Action.async at your create article method), so you'd possibly need to wait at test code result before trying to see if it's OK.
I had a similar problem and the solution is pointed here. In my case it was because I using response.asJson() twice, and as described by #jroper
Considering that the body of an HTTP response is a stream, and not
something that you want to necessarily buffer in memory, then it makes
sense that accessing the body twice (regardless of what format you are
accessing the body in) would not be supported.
Try instantiating your request inside the method definition.
This is how it's working for me (example using POST with JSON body and result as JSON):
"process" should {
"should be valid" in {
val request = FakeRequest(POST, "/").withJsonBody(Json.parse(""" {
"id":1,
"name":"prod 1",
"price":55.55
}"""))
val result: Future[Result] = controller.process.apply(request)
val json = contentAsJson(result)
status(result) must be(CREATED)
(json \ "id").as[Int] mustBe 1
// .. more assertions and rest of code ...
Here the code deals with a JsValue and query it's nodes to check if the returned values are matching the desired mock data output

Handling JSON requests in Play Framework 2.0 Scala

I am trying to send data from the client to the server using a JSON request. The body of the JSON request looks like this:
{
"upload":
{
"ok":"some message",
"assemblyId":"a9d8f72q3hrq982hf98q3"
}
}
Play is able to recognize the request body as JSON but when I try to parse individual values, namely the "upload" object, Play complains that it can't find the specified parameter.
The Scala method is as follows:
def add(course:Long) = withAccount { account => implicit request =>
println()
println(request.body) // output: AnyContentAsJson({"upload":{"ok":"ASSEMBLY_COMP...
request.body.asJson.map { json =>
println()
println(json) // output: {"upload":{"ok":"ASSEMBLY_COMPLETED","assemb...
(json \ "upload").asOpt[models.SomeClass].map { upload =>
Ok("Got upload")
}.getOrElse {
BadRequest("Missing parameter [upload]")
}
}.getOrElse {
BadRequest("Expecting Json data")
}
}
I'm having trouble understanding why the above code fails. The method has no trouble mapping the request body to a json object. The "println(json)" command prints out the exact same thing that Chrome shows me as the 'Request Payload'. Yet, when I try to grab the root object, "upload", it fails. And the method returns a bad request complaining about the missing parameter.
To do asOpt[models.SomeClass] there needs to be a Reads instance for it to work.
Here is an example
case class SomeClass(ok: String, assemblyId: String)
implicit object SomeClassReads extends Reads[SomeClass] {
def reads(json: JsValue) =
SomeClass((json \ "ok").as[String], (json \ "assemblyId").as[String])
}
You can see how you would implement a Reads instance at
https://github.com/playframework/Play20/blob/2.0.x/framework/src/play/src/main/scala/play/api/libs/json/Reads.scala#L35
If you use play 2.1x, Reads has changed a bit from 2.0x and it's probably your main problem(like me).
You can find a very good explanation here.
Simply this code works fine:
import play.api.libs.json._
import play.api.libs.functional.syntax._
case class Person(name: String, age: Int, lovesChocolate: Boolean)
implicit val personReads = Json.reads[Person]
It look amazing isn't it? But there are some points that you should pay attention:
Implicit definition should be in controller. Of course there are some other ways to do it.
If your model is in models class(it's in controller at the example above) you shouldn't name your object same with your class. In that case it doesn't work:
case class Person(name: String, age: Int, lovesChocolate: Boolean)
object Person{....} //this won't work
This way have big advantages. I strongly recommend you to check out this blog.