Working with AWS Lambdas, and when one fails due to an exception, AWS serializes that exception as JSON and sends it back to whatever invoked that Lambda. Here's what that might look like:
{
"errorMessage":"USER_SERVICE_FAILURE",
"errorType":"com.company.project.lambda.core.exceptions.LambdaFailureException",
"stackTrace":[
"com.company.project.lambda.worker.MainWorkerLambda.handleRequest(AccountWorker.java:127)",
"sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
"sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)",
"sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)",
"java.lang.reflect.Method.invoke(Method.java:498)"
],
"cause":{
"errorMessage":"some other exception error message",
"errorType":"com.company.project.lambda.core.exceptions.SomeOtherException",
"stackTrace":[
"insert other stack trace strings here...",
"...",
"..."
],
"cause":{
"errorMessage": "...",
...continue in to perpetuity...
}
}
}
The errorMessage, errorType, and stackTrace fields are easy enough to deserialize - they will always be a single String, a single String, and a List<String> respectively.
Where I'm stuck is the cause field. This could be an empty object if there is no cause, or one nested exception, or two, or a hundred...
How do I deserialize this? Here's my POJO so far.
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ExceptionRep {
String errorMessage;
String errorType;
List<String> stackTrace;
// how do I do the cause field?
}
Define your class as a node structure that has an attribute of its same type:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ExceptionRep {
String errorMessage;
String errorType;
List<String> stackTrace;
private ExceptionRep cause;
}
I would simply reuse your ExceptionRep class for the reference type, and annotate the object as optional (so if there is no cause in the JSON, it won't fail de-serialization).
Something like:
#JsonProperty(required=false)
ExceptionRep child;
This way if there are nested causes, they are recursively de-serialized.
If there are none, the property is ignored.
You can use a HashMap if you don't want to have to define all the properties in a custom class.
Related
I'm trying to create a simple SpringBoot REST api following hyperskill.org's "Web Quiz Engine" project.
But the Solve endpoint refuses to work.
Here's the controller:
#CrossOrigin(origins = "*")
#RestController
#RequestMapping("api")
#Validated
public class QuizController {
//snip
#PostMapping(value = "/quizzes/{id}/solve")
public Result solveQuiz(#PathVariable Long id, #RequestBody int[] answer){
return quizService.solveQuiz(id, answer);
}
}
Here's my postman :
stackoverflow disallows embedding images
POST http://localhost:8889/api/quizzes/1/solve?id=1
Body *raw
{
"answer": [2]
}
And the error:
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `[I` from Object value (token `JsonToken.START_OBJECT`); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type [I from Object value (token `JsonToken.START_OBJECT`)
I get the same error when I run the provided tests. Everything works until it try's to call Solve. What's most confusing is the type `[I`. What is going on?
Edit: Tried to follow the answer below:
#PostMapping(value = "/quizzes/{id}/solve")
public Result solveQuiz(#PathVariable Long id, #Valid #RequestBody AnswerArray answer){
return quizService.solveQuiz(id, answer.getAnswers());
}
and AnswerArray:
#Data
public class AnswerArray {
#NotNull
private List<Integer> answers;
}
Now the error is:
Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument 1 in public engine.Entities.Result engine.Controller.QuizController.solveQuiz(java.lang.Long,engine.Entities.AnswerArray): [Field error in object 'answerArray' on field 'answers': rejected value [null]; codes [NotNull.answerArray.answers,NotNull.answers,NotNull.java.util.List,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [answerArray.answers,answers]; arguments []; default message [answers]]; default message [must not be null]] ]
I also tried AnswerArray with an int[] and an Integer[] and also got null values. What am I missing?
well you are not posting an int[]
this is your body:
{
"answer": [
2
]
}
Which is actually a Object containing a list of integers
so your Java object should look as follows:
// The class can be named whatever
public class Request {
private List<Integer> answer;
// constructor
// getters and setters
}
And the function should look like:
public Result solveQuiz(#PathVariable Long id, #RequestBody Request answer){
return quizService.solveQuiz(id, answer.getAnswer());
}
The int[] does need to be inside a class, but the other answer left out a critical bit of information: that class needs a no-arg constructor that has an empty body. The default constructor works, but if you add an explicit constructor (or use Lombok's #Data), you no longer get the default. So adding a default constructor seems to be what I ultimately needed (by use of adding Lombok's
#NoArgsConstructor). Although I had tried that before, but Spring's lack of useful error info caused me to abandon it when there was another problem simultaneously.
In summary solveQuiz needed the int[] wrapped in an object. And that object needs an empty no-arg constructor (or default constructor), and Getter and Setter. (or Lombok #NoArgsConstructor)
So Here's my wrapper object:
#Data
#NoArgsConstructor
public class AnswerArray {
#NotNull
private int[] answer;
}
and my api function:
#PostMapping(value = "/quizzes/{id}/solve")
public Result solveQuiz(#PathVariable Long id, #Valid #RequestBody AnswerArray answer){
return quizService.solveQuiz(id, answer.getAnswer());
}
The problem may also be intellij not correctly compiling the changes, A "Rebuild Project" might have ultimately been the fix.
I have to work with an API that returns all objects wrapped in a unnamed root object. Something like this:
{
"user": {
"firstname":"Tom",
"lastname":"Riddle"
}
}
Here, I am interested in deserializing the user object only. But given the nature of the response, I will have to write a class that wraps the user object if I want to deserialize it successfully.
#Getter
#Setter
#ToString
// Wrapper class
public class Info {
private User user;
}
and then
#Getter
#Setter
#ToString
public class User {
private String firstname;
private String lastname;
}
All responses of the API return the response in this manner, so I am looking for a way to deserialize the response in such a way as to have one generic wrapper class that can be used to extract any type of JSON object.
I have tried this:
#Getter
#Setter
public class ResponseWrapper<T> {
private T responseBody;
}
and then
ResponseWrapper<User> userInfo = objectMapper.readValue(response.body().string(), ResponseWrapper.class);
But this results in the following exception:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "user" (class com.redacted.response.ResponseWrapper), not marked as ignorable (one known property: "responseBody"])
So, is there any way for me to deserialize this response without having to write separate wrapper classes for each API response like this?
You can do something like this:
JsonNode jsonNode = objectMapper.readTree(response.body().string());
String content = jsonNode.elements().next().toString();
User user = objectMapper.readValue(content, User.class);
Output:
User(firstname=Tom, lastname=Riddle)
I have a simple POJO:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class StatusPojo {
private String status;
}
When I de-serialize simple string "asd" (without quotes) like this:
StatusPojo pojo = new ObjectMapper().readValue("asd", StatusPojo.class)
I am getting a StatusPojo object created successfully, with status field's value as "asd", though it is not valid JSON and nowhere has the field name "status" mentioned along.
Why is it behaving like this and how to disable this behavior and have object mapper throw an exception?
Your POJO has #AllArgsConstructor (maybe because of the #Builder) that then generates something like this:
public StatusPojo(String status) {
this.status = status;
}
When ObjectMapper then de-serializes plain string it uses that constructor to create object.
If you added some other property to your pojo, like just:
private String noAsdPlease;
there would be an exception, because ObjectMapper could not find creator, there would not be that above mentioned constructor but with two String params.
At quick glace DeserializationFeature does not have such a feature that disables using one string arg constructor for plain string.
Playing with more fields, removing #Builder & #AllArgsConstructor might resolve your problem but if you cannot change those ther might not be other options.
I have a DTO that extends an abstract base DTO with a message property and the getters and setters. Currently, when I run my service I get this JSON object as the output.
{
"message": null,
"id_key": "014",
"status_flag": null,
"modified_by": "Dev",
"modified_date": "2018-05-30"
}
The message field is being inherited. Is there a way to set the inherited field to be in its own subobject like this.
{
"_errors": {
"message": null
},
"id_key": "014",
"status_flag": null,
"modified_by": "Dev",
"modified_date": "2018-05-30"
}
I want to avoid creating a BaseDTO object and the getters and setters in the inheriting class. I am trying to refactor a lot of DTOs and can do a replace all to add extends BaseDTO to every one of them. Thanks!
Create an "Errors" class:
public class Errors {
#JsonProperty("message")
public String message;
...
}
and use it in you BaseDTO:
#JsonProperty("_errors")
public Errors errors;
So, every class that inherit BaseDTO will have the "_errors" property with "message" property inside.
so I am getting a JsonMappingException with what I consider to be a weird error:
org.codehaus.jackson.map.exc.UnrecognizedPropertyException:
Unrecognized field "CoTravellers" (Class JobPush), not marked as
ignorable at [Source: java.io.StringReader#416a60a0; line: 1, column:
947] (through reference chain: >JobPush["CoTravellers"])
Now, what I have is this:
ProtocolContainer --> JobPush (inherits from DataPacket) --> Job --> CoTravellers
The JobPush, mentioned in the error above, is a sub-class of DataPacket. So, the ProtocolContainer has one DataPacket, and I have several classes inheriting DataPacket, where JobPush is one.
The JobPush is simple, looks like this:
public class JobPush extends DataPacket
{
public Job Job;
}
and it is in the Job-class that the CoTravellers field exists, not in the JobPush:
public class Job implements Serializable
{
#JsonDeserialize(using=CustomMapCoTravellerDeserializer.class)
public Map<Objects.CoTravellers, Integer> CoTravellers;
// ....
}
As you can see, I am trying to use a custom deserializer (see here for reference).
Now, I cannot understand why I get an error saying that there is no field "CoTravellers" in JobPush? I never said that CoTravellers is in the JobPush, as it is inside the Job-class.
The JSON I am parsing looks like this (this is cropped a bit for clarity, where SubPacket is the variable name, holding the DataPacket which in this case is a JobPush):
"SubPacket":{
"__type":"JobPush:#DataPackets.ToVehicle",
"Job":{
"CoTravellers":[
{
"Key":{
"CoTravellerId":0,
"Name":"Medresenär"
},
"Value":1
}
]
}
}
Anyone out there who can clue me in? =)
---- EDIT 1 ----
Adding some stuff, for clairity:
So, classes inherit from the class "DataPacket", and thus I have used #JsonSubTypes annotation to deal with that. This is the DataPacket-class:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="__type")
#JsonSubTypes(
{
#JsonSubTypes.Type(value = JobPush.class, name = "JobPush:#DataPackets.ToVehicle"),
})
public class DataPacket
{
}
you talk about a CoTraveller field, but in the JSON it is CoTravellers with an 's' on the end.