Deserialization exception while POSTing nested json to Spring controller - java

I'm using Spring, following is my controller code:
#RequestMapping(value = "/campaigns/addTask", method = RequestMethod.POST)
public Campaign addTaskToCampaign(#RequestParam(value = "campaignName")String campaignName,#Valid #RequestBody Task task) {
Campaign campaign = campaignInterface.findByName(campaignName);
if (campaign!=null){
List<String> task_ids;
if (campaign.getTask_ids()==null){
task_ids = new ArrayList<>();
}else{
task_ids= campaign.getTask_ids();
}
Task newTask = taskInterface.save(task);
task_ids.add(newTask.getId());
campaign.setTask_ids(task_ids);
return campaignInterface.save(campaign);
}
return null;
}
Where my task model is:
#Document(collection = "tasks")
public class Task {
#Id
private String id;
private String name;
private int points;
private List<Question>questions;
private List<String>answers;
...
}
And the nested question model is:
public class Question {
private Boolean isText = false;
private String questionText;
}
But, the same model when POSTing as nested json throws an HTTP 400 exception saying json unreadable exception, and it tried to parse the String questionText field as a boolean value.
Here is what im POSTing:
{
"name" : "Test Task 3",
"questions": [{ "questionText":"What is the name you college festival?","isText":true}]
}
And the exception that comes is this:
{
"timestamp": 1497508476467,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.http.converter.HttpMessageNotReadableException",
"message": "JSON parse error: Can not deserialize value of type boolean from String \"What is the name you college festival?\": only \"true\" or \"false\" recognized; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not deserialize value of type boolean from String \"What is the name you college festival?\": only \"true\" or \"false\" recognized\n at [Source: java.io.PushbackInputStream#6a371c03; line: 3, column: 32] (through reference chain: com.frapp.CBM.prod.model.Task[\"questions\"]->java.util.ArrayList[0]->com.frapp.CBM.prod.model.Question[\"questionText\"])",
"path": "/campaigns/addTask"
}
Any help is appreciated. ive been trying for hours. Thank you, in advance.

The first comment I would make to any of my developers is: please can you rename your boolean. This is because the getter will look like: isIsText().
As a general rule it is good practice to avoid starting a field name with "get", "set", or "is".
This is because these are the prefixes of java-beans properties.

#RequestMapping(value = "/campaigns/addTask", method = RequestMethod.POST)
public Campaign addTaskToCampaign(#RequestParam Map<String,Object> campaignName,#Valid #RequestBody Task task){
/*
if there is exception then just remove Task put it into json to catch into map.
access just as map by campaignName.get("key_name");
*/
Campaign campaign = campaignInterface.findByName(campaignName);
if (campaign!=null){
List<String> task_ids;
if (campaign.getTask_ids()==null){
task_ids = new ArrayList<>();
}else{
task_ids= campaign.getTask_ids();
}
Task newTask = taskInterface.save(task);
task_ids.add(newTask.getId());
campaign.setTask_ids(task_ids);
return campaignInterface.save(campaign);
}
return null;
}

Related

Write custom document to Cosmos DB with Java API

I have a Cosmos DB and want to write different kind of documents to it. The structure of the documents is dynamic and can change.
I tried the following. Let's say I have the following class:
class CosmosDbItem implements Serializable {
private final String _id;
private final String _payload;
public CosmosDbItem(String id, String payload) {
_id = id;
_payload = payload;
}
public String getId() {
return _id;
}
public String getPayload() {
return _payload;
}
}
I can create then the document with some JSON as follows:
CosmosContainer _container = ...
CosmosDbItem dataToWrite = new CosmosDbItem("what-ever-id-18357", "{\"name\":\"Jane Doe\", \"age\":42}")
item = _cosmosContainer.createItem(dataToWrite, partitionKey, cosmosItemRequestOptions);
This results in a document like that:
{
"id": "what-ever-id-18357",
"payload": "{\"name\":\"Jane Doe\", \"age\":42}",
"_rid": "aaaaaaDaaAAAAAAAAAA==",
"_self": "dbs/aaaaAA==/colls/aaaaAaaaDI=/docs/aaaaapaaaaaAAAAAAAAAA==/",
"_etag": "\"6e00c443-0000-0700-0000-5f8499a70000\"",
"_attachments": "attachments/",
"_ts": 1602525607
}
Is there a way in generating the payload as real JSON object in that document? What do I need to change in my CosmosDbItem class? Like this:
{
"id": "what-ever-id-18357",
"payload": {
"name":"Jane Doe",
"age":42
},
"_rid": "aaaaaaDaaAAAAAAAAAA==",
"_self": "dbs/aaaaAA==/colls/aaaaAaaaDI=/docs/aaaaapaaaaaAAAAAAAAAA==/",
"_etag": "\"6e00c443-0000-0700-0000-5f8499a70000\"",
"_attachments": "attachments/",
"_ts": 1602525607
}
Here is my solution that I ended up. Actually it is pretty simple once I got behind it. Instead of using CosmosDbItem I use a simple HashMap<String, Object>.
public void writeData() {
...
Map<String, Object> stringObjectMap = buildDocumentMap("the-id-", "{\"key\":\"vale\"}");
_cosmosContainer.createItem(stringObjectMap, partitionKey, cosmosItemRequestOptions);
...
}
public Map<String, Object> buildDocumentMap(String id, String jsonToUse) {
JSONObject jsonObject = new JSONObject(jsonToUse);
jsonObject.put("id", id);
return jsonObject.toMap();
}
This can produce the following document:
{
"key": "value",
"id": "the-id-",
"_rid": "eaaaaaaaaaaaAAAAAAAAAA==",
"_self": "dbs/eaaaAA==/colls/eaaaaaaaaaM=/docs/eaaaaaaaaaaaaaAAAAAAAA==/",
"_etag": "\"3b0063ea-0000-0700-0000-5f804b3d0000\"",
"_attachments": "attachments/",
"_ts": 1602243389
}
One remark: it is important to set the id key in the HashMap. Otherwise one will get the error
"The input content is invalid because the required properties - 'id; ' - are missing"

How do I resolve com.fasterxml.jackson.databind.exc.MismatchedInputException?

I'm getting the below error while trying to post a request through Swagger. This works fine when I do it through Postman.
I'm using springBootVersion = '2.1.0.RELEASE', swaggerVersion = '2.9.2'.
This is my Json mapping class.
DataExport.java
#XmlRootElement(name = "DataExport")
public class DataExport
{
#JsonProperty("engineName")
private String engineName;
#JsonProperty("searchQuery")
private String searchQuery;
public DataExport()
{
super();
}
public DataExport(String aEngineName, String aSearchQuery)
{
super();
this.engineName = aEngineName;
this.searchQuery = aSearchQuery;
}
RestController.java
#RequestMapping(value = "/export", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
#ApiOperation(authorizations = {
#Authorization(value = "oauth") }, value = "Initiates the job for export", response = String.class)
#ApiResponses({ #ApiResponse(code = 200, message = "Request accepted."),
#ApiResponse(code = 500, message = "The export could not be started due to an internal server error.") })
public String getJobId(#RequestBody DataExport aDataExport, HttpServletResponse aResponse,
#Value("${com.xyz.dataexportmodule.defaultfilelocation}") final String aLocation)
throws Exception
{
LOG.info("Initializing export for Engine {} and Query {}", aDataExport.getEngineName(),
aDataExport.getSearchQuery());
String exportLocation = aLocation
....
I want to pass this JSON
{
"engineName":"ABC",
"searchQuery":"*"
}
But I'm getting this error:
2019-04-01 13:42:05,022 [https-jsse-nio-8443-exec-19] WARN o.s.w.s.m.s.DefaultHandlerExceptionResolver - Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct an instance of `com.xyz.dataexportmodule.persistence.entity.DataExport` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('string');
nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct an instance of `com.xyz.dataexportmodule.persistence.entity.DataExport` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('string')
at [Source: (PushbackInputStream); line: 1, column: 1]]
I'm unable to figure out what is the issue, somebody please help.
P.s: My Swagger Screenshot
enter image description here
Solution: After making these changes in DataExport class and removing Location variable in REST controller method, this issue got resolved.
#JsonIgnoreProperties(ignoreUnknown = true)
#ApiModel(value = "Data Export", description = "A JSON object corresponding to Data Export entity.")
public class DataExport
{
private String mEngineName;
private String mSearchQuery;
/**
* Default CTor.
*/
public DataExport()
{
}
#JsonCreator
public DataExport(#JsonProperty("engineName") String aEngineName,
#JsonProperty("searchQuery") String aSearchQuery)
{
mEngineName = aEngineName;
mSearchQuery = aSearchQuery;
}
Your screenshot show that you have two body, which can't be done.
I don't know what you're trying to do, but just for test remove the field aLocation, and it will be OK.

Firebase Android: how do I access params nested in data via RemoteMessage?

via this shape:
{
"to": "000",
"priority": "high",
"data": {
"title": "A Title",
"message": "A Message",
"link": {
"url": "http://www.espn.com",
"text": "ESPN",
}
}
}
how can I access "url" and "text"?
String messageLink = remoteMessage.getData().get("link");
gets me:
{"text":"ESPN","url":"http://www.espn.com"}
but how do I drill deeper?
remoteMessage.getData().get("link").get("text");
doesnt quite work... I have also attempted JSONObject:
JSONObject json = new JSONObject(remoteMessage.getData());
JSONObject link = json.getJSONObject("link");
but this gives me try catch errors...
Any help and direction as always is greatly appreciated!
I would use gson and define a model class. The remote message gives you a Map<String, String> and their is no matching constructor for creating a json object.
Add gson to your build.xml:
compile 'com.google.code.gson:gson:2.5'
Create a notification model:
import com.google.gson.annotations.SerializedName;
public class Notification {
#SerializedName("title")
String title;
#SerializedName("message")
String message;
#SerializedName("link")
private Link link;
public String getTitle() {
return title;
}
public String getMessage() {
return message;
}
public Link getLink() {
return link;
}
public class Link {
#SerializedName("url")
String url;
#SerializedName("text")
String text;
public String getUrl() {
return url;
}
public String getText() {
return text;
}
}
}
Deserialize a notification object from the remote message.
If all your custom keys are at the top level:
Notification notification = gson.fromJson(gson.toJson(remoteMessage.getData()), Notification.class);
If your custom json data is nested in a single key for example "data" then use:
Notification notification = gson.fromJson(remoteMessage.getData().get("data"), Notification.class);
Note in this simple case the #SerializedName() annotations are unnecessary since the field names exactly match the keys in the json, but if you for example have a key name start_time but you want to name the java field startTime you would need the annotation.
As simple as that:
String linkData = remoteMessage.getData().get("link");
JSONObject linkObject = new JSONObject(linkData);
String url = linkObject.getString("url");
String text = linkObject.getString("text");
Of course, together with proper error handling.
Faced this issue when migrating from GCM to FCM.
The following is working for my use case, so perhaps it will work for you.
JsonObject jsonObject = new JsonObject(); // com.google.gson.JsonObject
JsonParser jsonParser = new JsonParser(); // com.google.gson.JsonParser
Map<String, String> map = remoteMessage.getData();
String val;
for (String key : map.keySet()) {
val = map.get(key);
try {
jsonObject.add(key, jsonParser.parse(val));
} catch (Exception e) {
jsonObject.addProperty(key, val);
}
}
// Now you can traverse jsonObject, or use to populate a custom object:
// MyObj o = new Gson().fromJson(jsonObject, MyObj.class)

return any exception in json in rest api

Is there any simple methods to return exception in JSON using Rest api?
I've already googled this question, but all solutions i see, was about throwing exceptions during some calculations. But what if income parameters are wrong? I mean what if there is sone string instead of int input parameter?
I created some DTO class for input data:
#XmlRootElement
public class RequestDTO implements Serializable{
private static final long serialVersionUID = 1L;
#XmlElement(name = "request_id")
private String requestId;
#XmlElement(name = "site")
private List<String> sitesIds;
#XmlElement(name = "date_begin")
#JsonSerialize(using = DateSerializer.class)
#JsonDeserialize(using = DateDeserializer.class)
private Date dateBegin;
#XmlElement(name = "date_end")
#JsonSerialize(using = JsonDateSerializer.class)
#JsonDeserialize(using = JsonDateDeserializer.class)
private Date dateEnd;
#XmlElement(name = "volume")
private double volume;
// there is getters and setters
}
If i sent something like 'qwerty' instead of 'volume' field in my json request i'l see error message like Runtime. Is it possible to handle it in someway? I mean to return error in json with such structure?
public class ExceptionDTO {
private String shortExceptionMessage;
private String stackTrace;
public ExceptionDTO(String shotExceptionMessage, String stackTrace){
this.shortExceptionMessage = shotExceptionMessage;
this.stackTrace = stackTrace;
}
public String getShortExceptionMessage() {
return shortExceptionMessage;
}
public String getStackTrace() {
return stackTrace;
}
}
UPD1:
#Provider
#Singleton
public class ExceptionMapperProvider implements ExceptionMapper<Exception>{
#Override
public Response toResponse(final Exception e) {
StringBuilder trace = new StringBuilder();
IntStream.range(0, e.getStackTrace().length)
.forEach(i -> trace.append(e.getStackTrace()[i]).append('\n'));
ExceptionDTO exceptionMessage = new ExceptionDTO(
e.toString(),
trace.toString()
);
return Response.status(500).entity(exceptionMessage).build();
}
}
As it's not really clear if you are interested on checking if field or value of the payload is correct, here are a few ways to work with both.
If you want to check if the value for a field is correct (ie volume field value should be greater than zero etc), check out bean validation. This makes use of annotations on the fields you want to verify.
// for example
#Min(value = 0, message = "invalid message")
private double range;
To use your ExceptionDTO as error response whenever one of those validation fails, you can do so by creating an ExceptionMapper<ConstraintViolationException>. check it here for more details.
If you are checking for the invalid field (ie client sends ragne fields instead of range), have a look at the stack trace on what exception is being thrown. Then register an exception mapper with your ExceptionDTO as body.
For example, if UnrecognizedPropertyException is thrown then you can add:
#Provider
public class UnrecognizedPropertyExceptionMapper implements ExceptionMapper<UnrecognizedPropertyException> {
#Override
public Response toResponse(UnrecognizedPropertyException e) {
ExceptionDTO myDTO = // build response
return Response.status(BAD_REQUEST).entity(myDTO).build();
}
}
If you want to validate input parameters in the request, you should return status code 400 (Bad Request) along with the error details. You can simply send json
{ "error": { "message": "string received for parameter x, where as int expected" } with the response status code 400.
`
I did a bit of research and determined that the best way to encode a Java exception in JSON is to use a convention developed by Oasis that looks like this:
{
"error": {
"code": "400",
"message": "main error message here",
"target": "approx what the error came from",
"details": [
{
"code": "23-098a",
"message": "Disk drive has frozen up again. It needs to be replaced",
"target": "not sure what the target is"
}
],
"innererror": {
"trace": [ ... ],
"context": [ ... ]
}
}
}
details is a list that should have an entry for each nested cause exception in the chain.
innererror.trace should include the stack trace if you wish, as a list of string values.
The response status code should be 400 unless you have a good reason for making it something else, and the code in the structure should match whatever you sent.
Write one method to convert a Java exception to this format, and you are done. Use it consistently and your JS code will be able to handle and display the exception values.
More of the details of the other approaches evaluated and dismissed are covered in this blog post on JSON REST API – Exception Handling
https://agiletribe.purplehillsbooks.com/2015/09/16/json-rest-api-exception-handling/
Here is the java method to convert an exception to this format:
public static JSONObject convertToJSON(Exception e, String context) throws Exception {
JSONObject responseBody = new JSONObject();
JSONObject errorTag = new JSONObject();
responseBody.put("error", errorTag);
errorTag.put("code", 400);
errorTag.put("target", context);
JSONArray detailList = new JSONArray();
errorTag.put("details", detailList);
String lastMessage = "";
Throwable runner = e;
while (runner!=null) {
String className = runner.getClass().getName();
String msg = runner.toString();
runner = runner.getCause();
JSONObject detailObj = new JSONObject();
detailObj.put("message",msg);
int dotPos = className.lastIndexOf(".");
if (dotPos>0) {
className = className.substring(dotPos+1);
}
detailObj.put("code",className);
System.out.println(" ERR: "+msg);
detailList.put(detailObj);
}
JSONObject innerError = new JSONObject();
errorTag.put("innerError", innerError);
JSONArray stackList = new JSONArray();
runner = e;
while (runner != null) {
for (StackTraceElement ste : runner.getStackTrace()) {
String line = ste.getFileName() + ":" + ste.getMethodName() + ":" + ste.getLineNumber();
stackList.put(line);
}
stackList.put("----------------");
runner = runner.getCause();
}
errorTag.put("stack", stackList);
return responseBody;
}

Json Exception wrong type

I use Gson in my project. But it returns me error
String sig = PortalConfig.getSignature(method, callId, params);
String url = PortalConfig.getUrl(method, callId, sig, params);
String plainResponse = BaseClientCommunicator.executeGetMethod(url);
GsonBuilder builder = new GsonBuilder();
Gson gsonObject = builder.create();
response = gsonObject.fromJson(plainResponse, GetMenuResponse.class);
return response;
example I get a Server-response like this
{
"group":
[
{
"id": "206896",
"name": "Ryż",
"info": "xyz"
},
{
"id": "206897",
"name": "Buraki",
"info": {}
}
]
}
and i have error Expected a string but was BEGIN_OBJECT
Exception in thread "main" com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 16151
how should I handle this exception??
public class GetMenuResponse
{
#SerializedName("group")
private group[] group;
//method get and set
//to string method
}
public class group
{
#SerializedName("id")
private String id;
#SerializedName("name")
private String name;
#SerializedName("info")
private String info;
//method get and set
//to string method
}
I do not have access to the database, because I use the API
Problem is at line "info": {} in your json string.
Your class have private String info; String type and in your JSON string it is JSONObject.
It will try to convert JSONObject into String, which give error Expected a string but was BEGIN_OBJECT.GSON API cant able to cast JSONObject into JAVA String.
Value of info in first element of your array group is correct that is "info": "xyz" but same variable value in second element is different.
check value of info if it is String than you need to check your JSON response coming from server, if not than you need to change it's type into class variable.

Categories