I'm playing around with Kotlin and Spark, creating a RESTful web service. However I'm struggling to parse a JSON POST request. I have the following endpoint...
post("") { req, res ->
var objectMapper = ObjectMapper()
println(req.body())
val data = objectMapper.readValue(req.body(), User::class.java)
usersDao.save(data.name, data.email, data.age)
res.status(201)
"okies"
}
However I'm getting a 500 error, it's not actually printing an error, just returning a 500.
It seems to be this line val data = objectMapper.readValue(req.body(), User::class.java). I'm attempting to convert the json body into a user object. Here's my user object...
data class User(val name: String, val email: String, val age: Int, val id: Int)
Related
I do have get request over http from an angular based client side which expects as an answer an array of bytes from a java server.
angular.ts
downloadDocument(documentId: string) {
const params = new HttpParams().set('docId', documentId);
return this.httpClient.get<any>(`/downloadpdf/`,
{ params: params});
}
controller.java
#GetMapping("/downloadpdf")
public String downloadDocument(#RequestParam("docId") final String docId) {
String response = (new String(getBytesArray(docId)));
// getBytesArray returns a byte[]
// response correctly computed
return response;
}
Parsing error is encountered while transmitting over http:
"HttpErrorResponse":
message: 'Http failure during parsing for http://localhost...'
error: 'error: SyntaxError: Unexpected token % in JSON at position 0 at JSON.parse () at XMLHttpRequest.onLoad'
Any ideas why this is happening?
It's happening because you call the overload of get() that takes a generic parameter:
this.httpClient.get<any>(...)
This overload sets the response type to JSON, thus telling the HttpClient to parse the response body to JSON and to returned the generated object or array. Since you do not want to receive JSON, you must use another overload.
The documentation is your friend.
If you want to receive a Blob, for example, you would use the second overload, documented as returning an Observable<Blob>, and expecting options with responseType: 'blob'.
I have a request that looks like the following:
package pricing
import scala.beans.BeanProperty
class Request(#BeanProperty var name: String, #BeanProperty var surname: String) {
def this() = this(name="defName", surname="defSurname")
}
The handler is as follows:
package pricing
import com.amazonaws.services.lambda.runtime.{Context, RequestHandler}
import scala.collection.JavaConverters
import spray.json._
class ApiGatewayHandler extends RequestHandler[Request, ApiGatewayResponse] {
import DefaultJsonProtocol._
def handleRequest(input: Request, context: Context): ApiGatewayResponse = {
val headers = Map("x-foo" -> "coucou")
val msg = "Hello " + input.name
val message = Map[String, String]("message" -> msg )
ApiGatewayResponse(
200,
message.toJson.toString(),
JavaConverters.mapAsJavaMap[String, Object](headers),
true
)
}
}
which has been documented as:
functions:
pricing:
handler: pricing.ApiGatewayHandler
events:
- http:
path: pricing/test
method: get
documentation:
summary: "submit your name and surname, the API says hi"
description: ".. well, the summary is pretty exhaustive"
requestBody:
description: "Send over name and surname"
queryParams:
- name: "name"
description: "your 1st name"
- name: "surname"
description: ".. guess .. "
methodResponses:
- statusCode: "200"
responseHeaders:
- name: "x-foo"
description: "you can foo in here"
responseBody:
description: "You'll see a funny message here"
responseModels:
"application/json": "HelloWorldResponse"
well, this is a copy and paste from one of the tutorials. And it is not working.
I guess that the BeanProperty refers to body object properties; and this is what I can guess from the example here.
if I would like to have query strings?
A try was:
package pricing
import scala.beans.BeanProperty
import spray.json._
abstract class ApiGatewayGetRequest(
#BeanProperty httpMethod: String,
#BeanProperty headers: Map[String, String],
#BeanProperty queryStringParameters: Map[String, String])
abstract class ApiGatewayPostRequest(
#BeanProperty httpMethod: String,
#BeanProperty headers: Map[String, String],
#BeanProperty queryStringParameters: Map[String, String])
class HelloWorldRequest(
#BeanProperty httpMethod: String,
#BeanProperty headers: Map[String, String],
#BeanProperty queryStringParameters: Map[String, String]
) extends ApiGatewayGetRequest(httpMethod, headers, queryStringParameters) {
private def getParam(param: String): String =
queryStringParameters get param match {
case Some(s) => s
case None => "default_" + param
}
def name: String = getParam("name")
def surname: String = getParam("surname")
def this() = this("GET", Map.empty, Map.empty)
}
Which results in:
{
"message":"Hello default_name"
}
suggesting that the class has been initialized with an empty map in place of the queryStringParameters which was however submitted correctly
Mon Sep 25 20:45:22 UTC 2017 : Endpoint request body after
transformations:
{"resource":"/pricing/test","path":"/pricing/test","httpMethod":"GET","headers":null,"queryStringParameters":{"name":"ciao", "surname":"bonjour"},"pathParameters":null,"stageVariables":null,
...
Note:
I am following this path because I feel it would be convenient and expressive to replace the Map in #BeanProperty queryStringParameters: Map[String, String] with a type T, for example
case class Person(#beanProperty val name: String, #beanProperty val surname: String)
However, the code above looks at {"name":"ciao", "surname":"bonjour"} as a String, without figuring out that it should deserialize that String.
EDIT
I have also tried to replace the scala map with a java.util.Map[String, String] without success
By default, Serverless enables proxy integration between the lambda and API Gateway. What this means for you is that API Gateway is going to pass an object containing all the metadata about the request into your handler, as you have noticed:
Mon Sep 25 20:45:22 UTC 2017 : Endpoint request body after transformations: {"resource":"/pricing/test","path":"/pricing/test","httpMethod":"GET","headers":null,"queryStringParameters":{"name":"ciao", "surname":"bonjour"},"pathParameters":null,"stageVariables":null, ...
This clearly doesn't map to your model which has just the fields name and surname in it. There are several ways you could go about solving this.
1. Adapt your model
Your attempt with the HelloWorldRequest class does actually work if you make your class a proper POJO by making the fields mutable (and thus creating the setters for them):
class HelloWorldRequest(
#BeanProperty var httpMethod: String,
#BeanProperty var headers: java.util.Map[String, String],
#BeanProperty var queryStringParameters: java.util.Map[String, String]
) extends ApiGatewayGetRequest(httpMethod, headers, queryStringParameters) {
AWS Lambda documentation states:
The get and set methods are required in order for the POJOs to work with AWS Lambda's built in JSON serializer.
Also keep in mind that Scala's Map is not supported.
2. Use a custom request template
If you don't need the metadata, then instead of changing your model you can make API Gateway pass only the data you need into the lambda using mapping templates.
In order to do this, you need to tell Serverless to use plain lambda integration (instead of proxy) and specify a custom request template.
Amazon API Gateway documentation has an example request template which is almost perfect for your problem. Tailoring it a little bit, we get
functions:
pricing:
handler: pricing.ApiGatewayHandler
events:
- http:
path: pricing/test
method: get
integration: lambda
request:
template:
application/json: |
#set($params = $input.params().querystring)
{
#foreach($paramName in $params.keySet())
"$paramName" : "$util.escapeJavaScript($params.get($paramName))"
#if($foreach.hasNext),#end
#end
}
This template will make a JSON out of the query string parameters, and it will now be the input of the lambda:
Endpoint request body after transformations: { "name" : "ciao" }
Which maps properly to your model.
Note that disabling proxy integration also changes the response format. You will notice that now your API returns your response model directly:
{"statusCode":200,"body":"{\"message\":\"Hello ciao\"}","headers":{"x-foo":"coucou"},"base64Encoded":true}
You can fix this by either modifying your code to return only the body, or by adding a custom response template:
response:
template: $input.path('$.body')
This will transform the output into what you expect, but will blatantly ignore the statusCode and headers. You would need to make a more complex response configuration to handle those.
3. Do the mapping yourself
Instead of extending RequestHandler and letting AWS Lambda map the JSON to a POJO, you can instead extend RequestStreamHandler, which will provide you an InputStream and an OutputStream, so you can do the (de)serialization with the JSON serializer of your choice.
I am new to Scala. I was trying to parse an API response in Scala. The API response is in the format:
{"items":[{"name":"john", "time":"2017-05-11T13:51:34.037232", "topic":"india", "reviewer":{"id":"12345","name":"jack"}},
{"name":"Mary", "time":"2017-05-11T13:20:26.001496", "topic":"math", "reviewer":{"id":"5678","name":"Tom"}}]}
My target is to populate a list of reviewer id's from the JSON response. I tried to create a JSON object from the response by
val jsonObject= parse(jsonResponse.getContentString()).getOrElse(Json.empty)
but couldn't get the reviewer ids from the json object. Even tried to iterate the JSON object, but didn't work.
I am not familiar with circe but here is how you would do it with spray-json
import spray.json._
import DefaultJsonProtocol._
val jsonResponse = """{"items":[{"name":"john", "time":"2017-05-11T13:51:34.037232", "topic":"india", "reviewer":{"id":"12345","name":"jack"}},{"name":"Mary", "time":"2017-05-11T13:20:26.001496", "topic":"math", "reviewer":{"id":"5678","name":"Tom"}}]}"""
Need to define the schema using case classes:
case class Reviewer(id: String, name: String)
case class Item(name: String, time: String, topic: String, reviewer: Reviewer)
case class Items(items: Array[Item])
And their implicit conversion:
implicit val reviewerImp: RootJsonFormat[Reviewer] = jsonFormat2(Reviewer)
implicit val itemConverted: RootJsonFormat[Item] = jsonFormat4(Item)
implicit val itemsConverted: RootJsonFormat[Items] = jsonFormat1(Items)
Then it's very simple, parsing is just this:
val obj = jsonResponse.parseJson.convertTo[Items]
At last, get the ids for the reviewers:
val reviewers = obj.items.map(it => it.reviewer.id)
You mentioned play, so here's how you could do it in Play
case class Reviewer(id:Long, name:String)
object Reviewer { implicit val format = Json.format[Reviewer] }
Once you have those set up you could either
val json:JsValue = Json.toJson(reviewerObject)
val json:JsObject = Json.toJson(reviewerObject).as[JsObject]
val json:String = Json.toJson(reviewerObject).toString // Valid json string
Or
val reviewer:Reviewer = Json.parse(reviewerJsonString).as[Reviewer]
val validates:Boolean = Json.parse(reviewerJsonString).validates[Reviewer]
I'm trying to create $http get request to fetch some json data generated by my web service, but it returns null error. However, the $http request works fine when I use this sample url instead (it returns json string too)
This is my angular code :
angular.module('ionicApp', ['ionic'])
.controller('ListCtrl', function ($scope, $http) {
$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
$http.get("http://localhost:8080/InventoryCtrl_Service/webresources/IVC_Service/GetUserList")
.then(function(response) {
console.log("success ");
},
function(response) {
console.log("Error : " + response.data + " Status : " + response.status);
}
});
This is my web service code :
#GET
#Path("/GetUserList")
#Produces("application/json")
public Response GetUserList() throws SQLException {
net.sf.json.JSONObject json = new net.sf.json.JSONObject();
JSONObject obj1 = new JSONObject();
JSONObject obj2 = new JSONObject();
JSONObject outerObject = new JSONObject();
JSONArray arr = new JSONArray();
obj1.put("Name", "Sara");
obj2.put("Name","David");
arr.add(obj1);
arr.add(obj2);
outerObject.put("records", arr);
return Response.status(200).entity(outerObject.toString()).build();
}
When I run the above code, it returns json string like this :
{"records":[{"Name":"Sara"},{"Name":"David"}]}
The console log returns this :
Error : null Status : 0
What is the meaning of the null error? Or is there anything wrong with how I return the json string?
Try using JSON_STRINGIFY, this will convert your incoming data into String format.
console.log(JSON_STRINGIFY(response.data));
TO verify what data your web service is returning, you can always check it by hitting your web service via postman.
I managed to solve this by adding CORS (Access-Control-Allow-Origin) to the response header, based on another SO answer. There's no problem with my angular code, it's just that I need to modify my web service code to enable the CORS. So I just modified the part where it returns data to become like this :
return Response.status(200).entity(outerObject.toString()).header("Access-Control-Allow-Origin", "*").build();
I was querying and getting data from database using Spring jdbctemplate rowmapper. Once I get the data I was mapping it to a Java object and converting it to a json object.
My json object contains contains a object with 2 fields
code
status
When I was getting the data from database I was passing 200 and success data to the above 2 fields and passing the json object as web service response.
My actual question is even if I didn't get any data, I need to pass the json object as web service response but with code as "404" amd message field as "no data" and other fields are to be empty strings ("") , similarly if any exception occurred, need to send json object with empty strings but with 503 code.
How can I do that?
My method code snippet:
userdtls= userDaoimpl.getUserdetails(userId);
if (userdtls== null) {
ResponseBuilder builder = Response.ok(convertToJson(userdtls),MediaType.APPLICATION_JSON).status(Status.NOT_FOUND).entity("No such user " + userdtls+);
throw new WebApplicationException(builder .build());
}
convertToJson.java
public String convertToJson(UserData userdtls) {
StatusData status = new StatusData ();
status.setCode("200");
status.setMessage("Success");
userdtls.setStatus(status);
PersonalData personal = new PersonalData();
personal.setdob("june11");
personal.setage("28");
userdtls.setPersonal(personal);
Gson gson = new GsonBuilder().serializeNulls().create();
return gson.toJson(userdtls));