Spring-Boot REST API can't parse int array - java

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.

Related

How to ignore field/column only when return response from spring boot

I need to ignore the field when return the response from spring boot. Pls find below info,
I have one pojo called Student as below
Student {
id,
name,
lastName
}
i am getting a body for as PostRequest as below
{
id:"1",
name:"Test",
lname:"Test"
}
i want get all the data from frontEnd (id,name,Lname) But i just want to return the same pojo class without id as below,
{
name:"Test",
lName:"Test"
}
I have tried #JsonIgnore for column id, But it makes the id column as null(id=null -it is coming like this even when i send data to id field from postman) when i get the data from frontEnd.
I would like to use only one pojo to get the data with proper data(withoud getting id as Null), and need to send back the data by ignoring the id column.
Is there any way to achieve it instead of using another pojo?
You just need to use #JsonInclude(JsonInclude.Include.NON_NULL) at class level and it will be helpful for ignore all your null fields.
For example :
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
// Fields
// Constructors
// Getters - setters
}
As of now you are using only one POJO it's not good practice because it's your main entity into your project, so good practice is always make DTO for the same.
This is possible via the #JsonView annotation that is part of Jackson. Spring can leverage it to define the views used on the controller.
You'd define your DTO class like this:
class User {
User(String internalId, String externalId, String name) {
this.internalId = internalId;
this.externalId = externalId;
this.name = name;
}
#JsonView(User.Views.Internal.class)
String internalId;
#JsonView(User.Views.Public.class)
String externalId;
#JsonView(User.Views.Public.class)
String name;
static class Views {
static class Public {
}
static class Internal extends Public {
}
}
}
The Views internal class acts as a marker to jackson, in order to tell it which fields to include in which configuration. It does not need to be an inner class, but that makes for a shorter code snippet to paste here. Since Internal extends Public, all fields marked with Public are also included when the Internal view is selected.
You can then define a controller like this:
#RestController
class UserController {
#GetMapping("/user/internal")
#JsonView(User.Views.Internal.class)
User getPublicUser() {
return new User("internal", "external", "john");
}
#GetMapping("/user/public")
#JsonView(User.Views.Public.class)
User getPrivateUser() {
return new User("internal", "external", "john");
}
}
Since Spring is aware of the JsonView annotations, the JSON returned by the /public endpoint will contain only externalId and name, and the /internal endpoint will additionally include the internalId field.
Note that fields with no annotation will not be included if you enable any view. This behaviour can be controlled by MapperFeature.DEFAULT_VIEW_INCLUSION, which was false in the default Spring ObjectMapper when I used this for the last time.
You can also annotate your #RequestBody parameters to controller methods with JsonView, to allow/disallow certain parameters on input objects, and then use a different set of parameters for output objects.

Add custom validation message to enum

Is it possible to add a custom message to an enum if validation fails?
I have this enum class:
public enum EngineType{
FOO('F'),
BAR('B'),
QUX('Q');
private char id;
EngineType(char id) {
this.id = id;
}
public char getId() {
return this.id;
}
}
My model class contains private MyEnum myEnum;.
Currently if a value is passed in which isn't a valid enum, I get this BindingException:
{
"status": 400,
"validationErrors": {
"myEnum": "Failed to convert property value of type 'java.lang.String' to required type 'ie.aviva.services.motor.cartellservice.model.EngineType' for property 'myEnum'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#javax.validation.constraints.NotNull ie.aviva.services.motor.cartellservice.model.EngineType] for value 'corge';"
},
"title": "Bad Request"
}
My controller looks like this:
#RequestMapping(
method = RequestMethod.GET,
value = Endpoints.TRUE_MATCH,
produces = {"application/json"})
public ResponseEntity<ResponseWrapper<List<TrueMatch>>> getTrueMatch(
#Valid MyDetails MyDetails) {
LogContext.put(Constants.TAG, myDetails);
LOG.info(
"Controller called to get true match with my details: " + myDetails.toString());
...
}
MyDetails is like this:
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#SuperBuilder
public class MyDetails extends BasicDetails {
#NotBlank
#Pattern(
regexp = "^[a-zA-Z]*$",
message = "'${validatedValue}' contains unsupported characters")
private String name;
#NotNull private MyEnum myEnum;
...
}
Is it possible to change the message to some custom message of my own?
I'm already able to do this in annotations that I added to other variables by including the message parameter in the annotation. I tried creating an annotation to validate the pattern as seen here but it didn't work. I think because the annotation was added like this, which was too late. The exception had already been thrown:
#NotNull
#EnumNamePattern(regexp = "foo|bar|qux")
private MyEnum myEnum;
Sure. The simplistic solution would be to simply accept the value as a String, then wrap your parsing logic in a try-catch block, then return the result you want the user to see.
final MyEnum userValue;
try
{
userValue = MyEnum.valueOf(someStringInput);
}
catch (Exception e)
{
//respond to the user, either with an exception, or
//a proper response according to your application
}
Bean Validation & ControllerAdvice
You can make use of the Bean Validation and Exception-handling mechanisms provided by spring.
Note that OP is already using Bean validation in their project, which can be observed by the usage of annotations #NotBlank, #Pattern. So the following reference is exclusively for the Readers.
To include the Bean validation into your Spring Boot project, you can add Spring Boot Starter Validation dependency. In a nutshell, Bean validation is a specification (like for instance JPA) describing an IPA, which offers various annotations and interfaces for verifying that the data of domain objects is correct. And Hibernate Validator is an implementation of this specification which comes with Spring Boot Starter Validation.
To validate that the given String matches one of the enum-constants we would need a separate class, let's say MyDetailsDto. In case if you wonder why a new class? If the validation constant would be applied on the field of type enum it would validate only input that can be successfully parsed into an enum (otherwise an IllegalArgumentException would be thrown before applying the validation annotation), which can be useful only certain enum-constants can be assigned to a field. But that's not the case, we need to find out whether the given string is equal to one of the enum constants (by the way, in addition this approach creates an opportunity for sanitizing the input, for instance replacing some characters if needed).
#Getter
#Setter
public class MyDetailsDto {
private String name;
#EngineTypeConstraint
private String engineType;
public MyDetails toMyDetails() {
return new MyDetails(name, EngineType.valueOf(engineType));
}
}
So, to create a custom Validation constraint, we need two things: a custom annotation a Validator associated with it (for more information refer to the documentation - Configuring Custom Constraints):
Each bean validation constraint consists of two parts:
A #Constraint annotation that declares the constraint and its configurable properties.
An implementation of the jakarta.validation.ConstraintValidator interface that implements the constraint’s behavior.
We can find more information regarding the requirements for the custom annotation in the Bean validation documentation Constraint:
Each constraint annotation must host the following attributes:
String message() default [...]; which should default to an error
message key made of the fully-qualified class name of the constraint
followed by .message. For example
"{com.acme.constraints.NotSafe.message}"
Class<?>[] groups() default {}; for user to customize the targeted groups
Class<? extends Payload>[] payload() default {}; for extensibility purposes
So our custom annotation needs to have at least these three attributes, and we're interested primarily in the first one, providing a message which would be used in case if an exception occurs. Let's our annotation:
#Constraint(validatedBy = EngineTypeValidator.class)
#Documented
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface EngineTypeConstraint {
String message() default "Engine type does not exist";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator should implement generic interface ConstraintValidator, specifying the associated annotation and the validated type as its parameters.
That's how the implementation might look like:
public class EngineTypeValidator implements ConstraintValidator<EngineTypeConstraint, String> {
private static final Set<String> TYPE_NAMES =
EnumSet.allOf(EngineType.class).stream().map(Enum::name).collect(Collectors.toSet());
#Override
public boolean isValid(String engineType,
ConstraintValidatorContext constraintValidatorContext) {
if (!TYPE_NAMES.contains(engineType)) throw new EngineTypeNotValidException(engineType);
return true;
}
}
As you have probably noticed, validator throws a custom exception EngineTypeNotValidException which receives an invalid engine model as a parameter. We need this exception in order to build a response based on it.
public class EngineTypeNotValidException extends RuntimeException {
public EngineTypeNotValidException(String type) {
super(String.format("Engine type '%s' doesn't exist.", type));
}
}
To handle this exception and produces an error-response by using Controller Advice. For that, we need to define a class annotated with #ControllerAdvice and create a method marked with #ExceptionHandler to target the custom exception defined above.
#ControllerAdvice(assignableTypes = MyDetailsController.class)
public class ValidationExceptionHandler {
#ExceptionHandler(EngineTypeNotValidException.class)
public ResponseEntity<ErrorResponse> handleInvalidEngine(RuntimeException e) {
Throwable cause = e.getCause();
return ResponseEntity
.badRequest()
.body(new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
HttpStatus.BAD_REQUEST.getReasonPhrase(),
List.of(cause.getMessage())
));
}
}
Finally, to customize the response body, we can create a POJO with a couple of string field for representing the information of a failing response.
#AllArgsConstructor
#Getter
public class ErrorResponse {
private int status;
private String message;
private List<String> errors;
}
Demo
That's it now we can give it a try.
Consider the following dummy Controller:
#RestController
public class MyDetailsController {
#PostMapping("/newMyDetails")
public String newCar(#RequestBody #Valid MyDetailsDto dto) {
// some business logic
return "is valid";
}
}
And here's a couple screenshots from Postman with responses:
Valid request (hard-coded message from the Controller in the response):
Invalid request (response with a customized error message, prompting that enum-name provided in the request doesn't exist):

Java Jsonb #JsonbNillable not functioning as expected

I am using Jsonb annotations to consume and produce JSON in a RESTful service. While producing the JSON from a class and using #JsonbNillable it doesn't appear to return null value for a field. I know the below snippets are not the full code but for demonstration purposes only as there are other fields filled so there is JSON to return. I am wondering if I am using the annotations in an incorrect manner.
Expected JSON
{
"my_id" : null
}
Attempt 1:
The following will return the field if not null, but when null it will not display
public class MyClass {
#JsonbNillable
#JsonbProperty(value = "my_id")
public String myId;
}
Attempt 2:
The following will return the field if not null or null, however, the field name is just myId and not the expected "my_id".
public class MyClass {
#JsonbNillable
public String myId;
}
Attempt 3:
The following will return the field if not null or null and have the field in the proper syntax. The expected outcome here is exactly what I am looking for. My concern here is that the nillable inside the #JsonbProperty is marked as deprecated in favor of #JsonbNillable.
public class MyClass {
#JsonbProperty(value = "my_id", nillable = true)
public String myId;
}
It is like I cannot use #JsonbNillable and #JsonbProeprty together. Is this by design? Is there something else that should be done differently?
Thank you in advance for any advise.

Validate an Enum with springframework validation Errors

TL;DR : Enum deserialization errors are not caught by org.springframework.validation.Errors in a Rest Controller
For reference: we didn't find a clean solution yet as we finally decided that no one should call us wit a bad enum
I have a rest controller that uses org.springframework.validation.Errors for parameter validations:
#RequestMapping(value = "/vol1/frodo")
public ResponseEntity<Object> simpleMethodUsingPost(
HttpServletRequest httpServletRequest,
#Valid #RequestBody MySimpleObject simpleObject,
Errors errors) {
/* If an error occured, I need to log the object */
if (errors.hasErrors()) {
List<FieldError> fields = errors.getFieldErrors();
doSomething(fields , simpleObject);
}
}
My class MySimpleObject looks like this:
public class MySimpleObject {
#Valid
#NotNull(message = "anObjectField is a mandatory field")
private EmbeddedObject anObjectField = null;
#Valid
#NotNull(message = "aStringField is a mandatory field")
private String aStringField = null;
#Valid
private MySimpleEnum aSimpleEnum = null;
}
And my enum class MySimpleEnum is basically a class with two values:
public enum MySimpleEnum{
ORC("ORC"),
URUK("URUK");
private String value;
MySimpleEnum(String value) {
this.value = value;
}
#Override
public String toString() {
return String.valueOf(value);
}
}
The validation of this object (and the injection of errors in the springframework Error object) works well when it's on a String or an Object, but it will fail validation of an enum (hence an object containing a valid-annoted enum will fail too).
It fails when trying to cast the JSON String to an enum when the value is not valid:
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error:
Cannot deserialize value of type 'lotr.middleearth.model.MySimpleEnum' from String "HOBBIT"
This deserialization error is caught if I use a ResponseEntityExceptionHandler and override handleHttpMessageNotReadable, but then I don't have access to the different other parameters and can't use them.
How can I configure either a Validator, enum or springframework Error so that this exception is caught and usable in my controller body?
I just came across the same problem but didn't like the idea of giving the user an unformatted "ugly" validation error message.
First, I made the enum property not nullable on the pojo.
#NotNull(message = "Type must be NEW_APPLICATION or RENEWAL")
private RegistrationSubmissionTypeEnum type;
Then I changed the setter to basically check the input (as a string) and see if it matches one of the enums. If not, I do nothing, the property stays null and it's reported back as one of the validation error messages (using the message text used on the #NotNull annotation).
public void setType(Object typeInput) {
for (RegistrationSubmissionTypeEnum typeEnum : RegistrationSubmissionTypeEnum.values()) {
if (typeEnum.getKey().equalsIgnoreCase(typeInput.toString())) {
this.type=RegistrationSubmissionTypeEnum.valueOf(typeInput.toString());
}
}
}
That's really the key. The normal behavior we all despise generates an ugly error message, but it also does it in a way such that this error message is displayed alone. Personally, I like to send back all errors en masse.
I'm not a fan of hardcoding the enum values on the #NotNull message, but in this particular case (small number of enum values), it's preferable to the default enum serialization error message, and the behavior of a one-off isolated error message.
I considered a custom validator, but that started to feel heavy. Maybe someone can improve on this.
The problem that is occurring is that in the enum MySimpleEnum there is no constant "HOBBIT" the possibilities are "ORC" and "URUK", in the validation question can be used simply as in the example:
#NotNull(message = "Custom message")
private MySimpleEnum aSimpleEnum
I ended up doing something like that to extract the problematic field in the request :
int start = ex.getMessage().indexOf("[\"");
int end = ex.getMessage().indexOf("\"]");
String fieldName = exception.getMessage().substring(start + 2, end)
The field happens to be at the end of the message between the brackets.
I'm not really proud of that one, it's messy, but it seems to be the only way with enums.
I guess it would be better to use strings and proper Spring validation instead, since it depends too much on the implementation and may break with future updates.

Java Jackson Deserialization of Nested Objects

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.

Categories

Resources