JsonInclude How to ignore default value boolean only when is not assigned - spring

when I use Include.NON_NULL, the error response is wrong and if I use Include.NON_DEFAULT, the response with status 200 is wrong
This is what I'm looking for:
Status: 200 OK
{
"adultMovie": false,
"backdropPathMovie": "/2U3hyiVzzhYzS6j9fcdVW4mO4Uk.jpg",
"originalLanguageMovie": "en",
"originalTitleMovie": "Fire"
}
Status: 400
{
"errors": [
{
"typeId": "FIELD_VALIDATION_ERRORS",
"field": "idMovie",
"message": "Invalid value of `fff` provided"
}
]
}
This is what I tried so far:
My model:
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Movie implements Serializable {
private static final long serialVersionUID = -3515253168981789136L;
private boolean adultMovie;
private String backdropPathMovie;
private String originalLanguageMovie;
private String originalTitleMovie;
private Set<Error> errors;
}
Status: 200 OK
{
"adultMovie": false,
"backdropPathMovie": "/2U3hyiVzzhYzS6j9fcdVW4mO4Uk.jpg",
"originalLanguageMovie": "en",
"originalTitleMovie": "Fire"
}
Status: 400. Validation error
{
"adultMovie": false, // It shouldn't show up
"errors": [
{
"typeId": "FIELD_VALIDATION_ERRORS",
"field": "idMovie",
"message": "Invalid value of `513f` provided"
}
]
}
If I try:
#JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class Movie implements Serializable {
Status: 200 OK
{
"backdropPathMovie": "/2U3hyiVzzhYzS6j9fcdVW4mO4Uk.jpg",
"originalLanguageMovie": "en",
"originalTitleMovie": "Fire"
} // Here should show up "adultMovie": false, as this field is set as false.
Status: 400. Validation error
{
"errors": [
{
"typeId": "FIELD_VALIDATION_ERRORS",
"field": "idMovie",
"message": "Invalid value of `fff` provided"
}
]
}

Instead on boolean you can use Boolean like below. boolean is having values either true or false and default value is false if not set. Therefore with #JsonInclude(JsonInclude.Include.NON_DEFAULT) even though you have set false value manually it will assume it's default value and will ignore this value even though you required this in response. On the other hand Boolean is having three values like true, false and null. So if you have set it to false it will also consider in in response. Only if you set to null it will not include in response.
private Boolean adultMovie;

Your question is not clear;
the question subject is the "subject" of the question,
it is not the text of the question.
Source of your problem: You must learn how to count.
Description:
A boolean may contain exactly two values; true and false.
Based on my imagination of what you wanted to ask,
it appears that you want something that contains three values;
true, false, and not-set.
Use Boolean as the type of the adultMovie field.
Boolean is a reference type;
therefore it can have three values;
Boolean.TRUE, Boolean.FALSE, and null.
In concert with the #JsonInclude(JsonInclude.Include.NON_NULL) annotation,
a null value for adultMovie will result in JSON that does not contain the "adultMovie" entry.

Related

Fluent Validation and ASP.NET Core 6 Web API

I am new to fluent validation and also a beginner in Web API. I have been working on a dummy project to learn and your advice will be much appreciated. After following the FluentValidation website, I was able to successfully implement fluent validation.
However, my response body looks very different and contains a lot of information. Is it possible to have a regular response body with validation errors?
I will put down the steps I took to implement fluent validation. your advice and help are much appreciated. I am using manual validation because based on the fluent validation website they are not supporting the auto validation anymore.
In the program file, I added
builder.Services.AddValidatorsFromAssemblyContaining<CityValidator>();
Then I added a class that validated my City class which has two properties Name and Description:
public class CityValidator : AbstractValidator<City>
{
public CityValidator()
{
RuleFor(x => x.Name)
.NotNull()
.NotEmpty()
.WithMessage("Please specify a name");
RuleFor(x => x.Description)
.NotNull()
.NotEmpty()
.WithMessage("Please specify a Description");
}
}
In my CitiesController constructor I injected Validator<City> validator; and in my action, I am using this code:
ValidationResult result = await _validator.ValidateAsync(city);
if (!result.IsValid)
{
result.AddToModelState(this.ModelState);
return BadRequest(result);
}
The AddToModelState is an extension method
public static void AddToModelState(this ValidationResult result, ModelStateDictionary modelState)
{
if (!result.IsValid)
{
foreach (var error in result.Errors)
{
modelState.AddModelError(error.PropertyName, error.ErrorMessage);
}
}
}
On post, I am getting the response as
{
"isValid": false,
"errors": [
{
"propertyName": "Name",
"errorMessage": "Please specify a name",
"attemptedValue": "",
"customState": null,
"severity": 0,
"errorCode": "NotEmptyValidator",
"formattedMessagePlaceholderValues": {
"PropertyName": "Name",
"PropertyValue": ""
}
},
{
"propertyName": "Description",
"errorMessage": "Please specify a name",
"attemptedValue": "",
"customState": null,
"severity": 0,
"errorCode": "NotEmptyValidator",
"formattedMessagePlaceholderValues": {
"PropertyName": "Description",
"PropertyValue": ""
}
}
],
"ruleSetsExecuted": [
"default"
]
}
While the regular response without Fluent Validation looks like this:
{
"errors": {
"": [
"A non-empty request body is required."
],
"pointofInterest": [
"The pointofInterest field is required."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-1a68c87bda2ffb8de50b7d2888b32d02-94d30c7679aec10b-00"
}
The question: is there a way from the use the fluent validation and get the response format like
{
"errors": {
"": [
"A non-empty request body is required."
],
"pointofInterest": [
"The pointofInterest field is required."
]
},
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-1a68c87bda2ffb8de50b7d2888b32d02-94d30c7679aec10b-00"
}
Thank you for your time.
Updated ans:
with your code, you can simply replace.
return BadRequest(result); // replace this line with below line.
return ValidationProblem(ModelState);
then you get same format as required.
------------------------*----------------------------------------
Please ignore this for manual validation.
You don't need explicit validation call.
this code is not required:
ValidationResult result = await _validator.ValidateAsync(city);
if (!result.IsValid)
{
result.AddToModelState(this.ModelState);
return BadRequest(result);
}
it will auto validate the model using your custom validator.
you simply need this
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
and it will give you errors in the require format.
if(!result.IsValid)
{
result.AddToModelState(this.ModelState);
return ValidationProblem(ModelState);
}

Does having a custom error response mean that you have to catch any exception in order to be consistent?

In my spring boot project i have a User class and its' fields have annotation constrains like #Size, #Pattern #NotNull etc.
For example
#Id
#Column(name = "name", nullable = false, length = 16, unique = true)
#NotNull
#Size(max = 16, message = "Username should be less or equal than 16 characters")
#Pattern(regexp = "[^\s]*", message = "Username should not contain whitespaces")
#Pattern(regexp = "^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$", message = "Username should should start with a letter")
private String userName;
A post request with invalid userName returns the following error response
{
"timestamp":"2021-06-28T18:02:02.720+00:00",
"status":400,
"error":"Bad Request",
"message":"Validation failed for object='user'. Error count: 1",
"errors":[
{
"codes":[
"Pattern.user.userName",
"Pattern.userName",
"Pattern.java.lang.String",
"Pattern"
],
"arguments":[
{
"codes":[
"user.userName",
"userName"
],
"arguments":null,
"defaultMessage":"userName",
"code":"userName"
},
[
],
{
"defaultMessage":"^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$",
"arguments":null,
"codes":[
"^[A-Za-zΑ-Ωα-ωΆ-Ώά-ώ].*$"
]
}
],
"defaultMessage":"Username should should start with a letter",
"objectName":"user",
"field":"userName",
"rejectedValue":"5",
"bindingFailure":false,
"code":"Pattern"
}
],
"path":"/signup"
}
Before questioning if this kind of error format is what i need, i didn't like it so i tried to make my own like in this guide Baeldung
I have a global controller now to deal with custom errors like when the username is taken.
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {...}
This is what i get now
{
"timestamp": "29-06-2021 05:05:03",
"status": "BAD_REQUEST",
"message": "Invalid input",
"path": "/signup",
"errors": [
{
"field": "userName",
"message": "Username should should start with a letter",
"rejectedValue": "5"
}
]
}
I suppose a good API means that you have to be consistent, that is we always have to return an error response with the same structure.
I have override some ResponseEntityExceptionHandler's method in order to catch other errors but there are still many methods to override. Here is a list of the remaining methods.
// handleBindException
// handleTypeMismatch
// handleMissingServletRequestPart
// handleMissingServletRequestParameter
// handleMethodArgumentTypeMismatch
// handleConstraintViolation
// handleHttpMediaTypeNotAcceptable
// handleMissingPathVariable
// handleServletRequestBindingException
// handleConversionNotSupported
// handleHttpMessageNotWritable
// handleAsyncRequestTimeoutException
My questions:
Do i have to catch all these exceptions? To be more specific, is it always possible to take all these exceptions no matter how your domains, controllers, services work?
Can you please write for each of these exceptions a bad request that will cause them to be thrown? Please, don't just tell me when they will be thrown. I'm new to spring and i won't be able to understand without an example.
How about overriding the below method from ResponseEntityExceptionHandler, as it is being invoked by all methods mentioned in the query.
protected ResponseEntity<Object> handleExceptionInternal()
And have your own logic to check the instance of exception and provide different kind of error response to client.

How I can return my custom json file instead of default json file that generates spring boot?

I have a rest controller for authorization:
#RestController
class AuthController {
#PostMapping("/sign-up")
fun signUp(#RequestBody signUpRequest: SignUpRequest): ResponseEntity<String> {
some code here..
}
}
The signUp method gets SignUpRequest model as a request body. SignUpRequest model is:
enum class Role {
#JsonProperty("Student")
STUDENT,
#JsonProperty("Tutor")
TUTOR
}
data class SignUpRequest(
val role: Role,
val email: String,
val password: String
)
When I make /sign-up post request with JSON:
{
"role": "asdf",
"email": "",
"password": ""
}
It returns me an answer that were generated by spring boot:
{
"timestamp": "2020-02-12T05:45:42.387+0000",
"status": 400,
"error": "Bad Request",
"message": "JSON parse error: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `foo.bar.xyz.model.Role` from String \"asdf\": not one of the values accepted for Enum class: [Student, Tutor]\n at [Source: (PushbackInputStream); line: 3, column: 10] (through reference chain: foo.bar.xyz.model.SignUpRequest[\"role\"])",
"path": "/sign-up"
}
Question is: How I can return my custom JSON instead of that default generated JSON?
I want to return my custom JSON, like:
{
"result": "Invalid user data are given",
"errors": [
{
"fieldName": "ROLE",
"text": "Given role does not exist"
},
{
"fieldName": "EMAIL",
"text": "EMAIL is empty"
}
]
}
I suggest you to create ErrorContrller that generates custom json map as response. Then when you will catch an error in sign-up method, call ErrorContrllers method.
You can find info from this link
Finally I found out a solution. You should create a class that annotates #ControllerAdvice, and make a method that annotates #ExceptionHandler.
#ControllerAdvice
class HttpMessageNotReadableExceptionController {
#ExceptionHandler(HttpMessageNotReadableException::class)
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
fun handleException(
exception: HttpMessageNotReadableException
): PostSignUpResponseError {
val errors = mutableListOf<PostSignUpResponseErrorItem>()
errors.add(
PostSignUpResponseErrorItem(
fieldNamePost = "Role",
text = "Given role does not exist"
)
)
return PostSignUpResponseError(
result = "Invalid user data are given",
errors = errors
)
}
}
where PostSignUpResponseErrorItem and PostSignUpResponseError are:
data class PostSignUpResponseError(
val result: String,
val errors: List<PostSignUpResponseErrorItem>
)
class PostSignUpResponseErrorItem(
val fieldNamePost: PostSignUpRequestFieldName,
val text: String
)
Anyway, I still don't know how to attach this thing to a certain PostMapping method.

How to remove unwanted keys from rest-assured response object and assert remaining object data with constant variable having json string using java

In rest-assured test cases I am getting response as mentioned, where I want to remove keys such as "updated_at", "deleted_at", "created_at" and "notice" and then assert this response object with expected json string constant which contains 'settings'
{
"notice": "The Settings are updated successfully.",
"settings": {
"push_notification": {
"enabled": true,
"credentials": [{
"key": "value"
}],
"service_name": "API Testing"
},
"created_at": "2019-05-04T14:52:32.773Z",
"deleted_at": "false",
"updated_at": "2019-05-07T11:23:22.781Z"
}
}
For given response the expected json string is...
public static String SETTING_EXPECTED = "{\"push_notification\": {\"enabled\": true, \"credentials\": [{\"key\": \"value\"}], \"service_name\": \"API Testing\"}}"
Please help me with creating a common method using java which can be reuse for response assertions in all the test cases.
To delete keys from response you can use below code I am using jayway jsonpath library, you need to pass Json Response and field name jsonPath, in case your it will be "$.settings.created_at" :
public String deleteFieldNameFromResponse(String jsonResponse, String fieldToDelete)
throws ParseException, FileNotFoundException, IOException {
Object obj = null;
JSONParser parser = new JSONParser();
JsonPath jsonPath = null;
DocumentContext docCtx = null;
obj = parser.parse(jsonResponse);
docCtx = JsonPath.parse(obj);
docCtx.delete(fieldToDelete);
jsonPath = JsonPath.compile("$");
return docCtx.read(jsonPath).toString();
}

Web API/JsonMediaTypeFormatter accepts Invalid JSON and passes null argument to action

I have the following model:
public class Resource
{
[DataMember(IsRequired = true)]
[Required]
public bool IsPublic { get; set; }
[DataMember(IsRequired = true)]
[Required]
public ResourceKey ResourceKey { get; set; }
}
public class ResourceKey
{
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemId { get; set; }
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemDataIdType { get; set; }
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemEntityType { get; set; }
[StringLength(50, MinimumLength = 1)]
[Required]
public string SystemDataId { get; set; }
}
I have the following action method signature:
public HttpResponseMessage PostResource(Resource resource)
I send the following request with JSON in the body (an intentionally invalid value for property "IsPublic"):
Request Method:POST
Host: localhost:63307
Connection: keep-alive
Content-Length: 477
User-Agent: Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.97 Safari/537.22
Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
{
"IsPublic": invalidvalue,
"ResourceKey":{
"SystemId": "asdf",
"SystemDataIdType": "int",
"SystemDataId": "Lorem ipsum",
"SystemEntityType":"EntityType"
},
}
This is invalid JSON - run it through JSONLint and it tells you:
Parse error on line 2:
{ "IsPublic": invalidvalue,
.................^ Expecting 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '['
The ModelState.IsValid property is 'true' - WHY???
Also, instead of throwing a validation error, the formatter seems to give up on deserializing and simply passes the 'resource' argument to the action method as null!
Note that this also happens if I put in an invalid value for other properties, e.g. substituting:
"SystemId": notAnObjectOrLiteralOrArray
However, if I send the following JSON with a special undefined value for the "SystemId" property:
{
"IsPublic": true,
ResourceKey:{
"SystemId": undefined,
"SystemDataIdType": "int",
"SystemDataId": "Lorem ipsum",
"SystemEntityType":"EntityType"
},
}
Then I get the following, reasonable, exception thrown:
Exception Type: Newtonsoft.Json.JsonReaderException
Message: "Error reading string. Unexpected token: Undefined. Path 'ResourceKey.SystemId', line 4, position 24."
Stack Trace: " at Newtonsoft.Json.JsonReader.ReadAsStringInternal()
at Newtonsoft.Json.JsonTextReader.ReadAsString()
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ReadForType(JsonReader reader, JsonContract contract, Boolean hasConverter)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)"
SO: what is going on in the Newtonsoft.Json library which results in what seems like partial JSON Validation???
PS: It is possible to post JSON name/value pairs to the Web API without enclosing the names in quotes...
{
IsPublic: true,
ResourceKey:{
SystemId: "123",
SystemDataIdType: "int",
SystemDataId: "Lorem ipsum",
SystemEntityType:"EntityType"
},
}
This is also invalid JSON!
OK - so it appears that part of the problem was caused by my own doing.
I had two filters on the controller:
Checks whether there are any null action parameters being passed to an action method and if so, returns a "400 Bad Request" response stipulating that the parameter cannot be null.
A ModelState inspection filter which checked the Errors of the ModelState and if any are found, return them in a "400 Bad Request" response.
The mistake I made was to put the null argument filter before the model state checking filter.
After Model Binding, the serialization would fail correctly for the first JSON example, and would put the relevant serialization exception in ModelState and the action argument would remain null, rightfully so.
However, since the first filter was checking for null arguments and then returning a "404 Bad Request" response, the ModelState filter never kicked in...
Hence it seemed that validation was not taking place, when in fact it was, but the results were being ignored!
IMPORTANT: Serialization exceptions that happen during Model Binding are placed in the 'Exception' property of the ModelState KeyValue pair Value...NOT in the ErrorMessage property!
To help others with this distinction, here is my ModelValidationFilterAttribute:
public class ModelValidationFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ModelState.IsValid) return;
// Return the validation errors in the response body.
var errors = new Dictionary<string, IEnumerable<string>>();
foreach (KeyValuePair<string, ModelState> keyValue in actionContext.ModelState)
{
var modelErrors = keyValue.Value.Errors.Where(e => e.ErrorMessage != string.Empty).Select(e => e.ErrorMessage).ToList();
if (modelErrors.Count > 0)
errors[keyValue.Key] = modelErrors;
// Add details of any Serialization exceptions as well
var modelExceptions = keyValue.Value.Errors.Where(e => e.Exception != null).Select(e => e.Exception.Message).ToList();
if (modelExceptions.Count > 0)
errors[keyValue.Key + "_exception"] = modelExceptions;
}
actionContext.Response =
actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, errors);
}
}
And here is the action method, with the filters in the correct order:
[ModelValidationFilter]
[ActionArgNotNullFilter]
public HttpResponseMessage PostResource(Resource resource)
So now, the following JSON results in:
{
"IsPublic": invalidvalue,
"ResourceKey":{
"SystemId": "asdf",
"SystemDataIdType": "int",
"SystemDataId": "Lorem ipsum",
"SystemEntityType":"EntityType"
},
}
{
"resource.IsPublic_exception": [(2)
"Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21.",
"Unexpected character encountered while parsing value: i. Path 'IsPublic', line 2, position 21."
]-
}
However, all of this does not explain why invalid JSON is still parsed by the JsonMediaTypeFormatter e.g. it does not require that names be strings.
More of a workaround than an answer, but I was able to get this to work using the workaround posted at http://aspnetwebstack.codeplex.com/workitem/609. Basically, instead of having your Post method's signature take a Resource instance, make it take no parameters and then use JSon.Net (or a new instance of JsonMediaTypeFormatter) to do the deserialization.
public void Post()
{
var json = Request.Content.ReadAsStringAsync().Result;
var resource = Newtonsoft.Json.JsonConvert.DeserializeObject<Resource>(json);
//Important world saving work going on here
}

Resources