Jackson deserialization of missing JSON number value to 0.0 in Spring - spring

I have defined a Kotlin data class like this:
#JsonIgnoreProperties(ignoreUnknown = true)
data class MandatoryLimits(
val upper: Double,
val lower: Double
)
as part of a compound object that my Spring service receives as a request body in a POST request. When I send a test request with one value missing, e.g. like this:
"specificationLimits": {
"lower": 1.6
}
then I receive a correctly deserialised object of Type MandatoryLimits, but the missing value is set to simply 0.0.
I would have expected to get a BAD REQUEST from the service, instead. Do I need to configure something in addition?
Thanks in advance for all your help!

To fail deserialization when a Kotlin primitive is null, you need to make sure the DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES Jackson configuration flag is active.
Below is an example of ObjectMapper bean configuration that will throw an error when it tries deserializing a null Kotlin primitive.
#Configuration
class JacksonConfig {
#Bean
fun objectMapper(): ObjectMapper {
return jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true)
}
}

As a workaround I defined the data class members as nullable and added an init Block to validate the presence of both values. In case of an error, I throw the response exception manually.

Related

Remove null attributes from request body Spring Boot

I'm new to spring boot and was referring to this article. It asks to add #JsonInclude(Include.NON_NULL) annotation to remove null attributes from response body, however I'm interested in removing null attributes from request body.
Please suggest how can I achieve that.
The request body is:
{
"userUuid": "2u9k2ld8f-ghj47dhj",
"suggestion": null
}
and the request DTO class is:
#Data
#NoArgsConstructor
public class UserRequestDTO implements Serializable {
private String userUuid;
#JsonInclude(JsonInclude.Include.NON_NULL)
private String suggestion;
}
Clearly it is not working for me. When I stringify and print the request body, it includes suggestion attr which is null.
Thank you for any help.
Well... Do not use toString() use Jackson ObjectMapper. So like:
var objectMapper = new ObjectMapper();
var dto = objectMapper.readValue(JSON, UserRequestDTO.class);
var strNoNullFields = objectMapper.writeValueAsString(dto);
That way the annotation is working.
Anyway this is not something very optimal. You might want to re-think your design how to calculate checksum (your question smells a bit like XY-problem).
I need to generate the checksum of the payload excluding the null attrs. I do not want to replace null attr rather remove it. To generate the signature I will to doing .toString() on the request body
I think you are looking for a request has dynamic attributes.
Try use a HashMap<String,Object> on parameters.
Other simple way is overriding toString() from DTO and ignore null attrs.
Suggestions: Besides using this only for logging I think both is bad practice could be better create a method generateSignature() handles all rules of this.

What does Spring use to do deserialization?

I have the following class:
data class Thing(val lines: List<String>)
The JSON representation is:
{
"lines": [
"something",
"something else"
]
}
Spring WebFlux can successfully parse this with the following:
// Parse the JSON as an object and return it.
request -> ServerResponse.ok().body(request.bodyToMono(Thing::class.java)
However, using Jackson directly with either of the following techniques fails:
val mapper = ObjectMapper()
val item = mapper.readValue<Thing>("""{"lines":["something","something else"]}""")
ServerResponse.ok().body(request.bodyToMono(Map::class.java)
.map { map ->
val mapper = ObjectMapper()
val tmp = mapper.convertValue(map, Thing::class.java)
}
The error is:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `Thing` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
I thought that Spring was using Jackson to do its transformation. And I thought that Jackson could cope with such basic transformations from JSON -> POJOs. Using a #JsonDeserialize class obviously works. So why does the first example work and the second not?
Spring uses Jackson but it registers custom handlers and modules when it creates its default instance of ObjectMapper.
Jackson has special handling for constructors that take a single argument. This was done to support classes like UUID and URI. To instruct Jackson to not use this technique, annotate your constructor with #JsonCreator.
data class Thing #JsonCreator constructor(val lines: List<String>)
I have not reviewed Spring's reactive code so I do not know what or if it does something to disable Jackson's special handling.

How to set header variables in GraphQL-SPQR

I'm running a GraphQL API using GraphQL-SPQR and Spring Boot.
At the moment, I am throwing RuntimeExceptions to return GraphQL errors. I have a customExceptionHandler that implements DataFetcherExceptionHandler that returns errors in the correct format, as shown below:
class CustomExceptionHandler : DataFetcherExceptionHandler {
override fun onException(handlerParameters: DataFetcherExceptionHandlerParameters?): DataFetcherExceptionHandlerResult {
// get exception
var exception = handlerParameters?.exception
val locations = listOf(handlerParameters?.sourceLocation)
val path = listOf(handlerParameters?.path?.segmentName)
// create a GraphQLError from your exception
if (exception !is GraphQLError) {
exception = CustomGraphQLError(exception?.localizedMessage, locations, path)
}
// cast to GraphQLError
exception as CustomGraphQLError
exception.locations = locations
exception.path = path
val errors = listOf<GraphQLError>(exception)
return DataFetcherExceptionHandlerResult.Builder().errors(errors).build()
}
}
I use the CustomExceptionHandler as follows (in my main application class):
#Bean
fun graphQL(schema: GraphQLSchema): GraphQL {
return GraphQL.newGraphQL(schema)
.queryExecutionStrategy(AsyncExecutionStrategy(CustomExceptionHandler()))
.mutationExecutionStrategy(AsyncSerialExecutionStrategy(CustomExceptionHandler()))
.build()
}
I'd like to set a header variable for a UUID that corresponds to the exception, for logging purposes. How would I do that?
Even better, is it possible to create a Spring Bean that puts the UUID in the header for all queries and mutations?
Thanks!
when you're using spring boot, there's two options:
you're using the spring boot graphql spqr starter (which brings it's own controller to handle all graphQL requests)
you're using plain graphql-spqr and have your own controller to handle GraphQL requests
In any case, you've got a few options:
Making your CustomExceptionHandler a Spring Bean and Autowiring HttpServletResponse
That would probably be the easiest way to go - and it would probably work in any case: You could simply make your CustomExceptionHandler a Spring bean and have it autowire the HttpServletRequest - in the handler method, you could then set it to whatever you would like it to be. Here's some dummy code in Java (sorry, I am not proficient enough in Kotlin):
#Component
class CustomExceptionHandler implements DataFetcherExceptionHandler {
private final HttpServletResponse response;
public CustomExceptionHandler(HttpServletResponse response) {
this.response = response;
}
#Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
response.setHeader("X-Request-ID", UUID.randomUUID().toString());
// ... your actual error handling code
}
}
This is going to work because spring will realise that HttpServletRequest differs for each request. It will therefore inject a dynamic proxy into your error handler that will point to the actual HttpServletResponse instance for every request.
I would argue, that it's not the most elegant way, but it will certainly solve your problem.
for the graphql-spqr spring boot starter
There's a default controller implementation that is used in projects using this starter. That controller will handle every graphql request that you receive. You can customise it, by implementing your own GraphQLExecutor and making it a spring bean. That executor is responsible to call the GraphQL engine, pass the parameters in and output the response. Here's the default implementation, that you might want to base your work on.
Similarly to the previous solution, you could autowire the HttpServletResponse in that class and set a HTTP Response header.
That solution would allow you to decide, if you want to set a request id in all cases, or just in specific error cases. (graphql.execute returns an object from which you can get the information if and what errors existed)
when using graphql-spqr without the spring boot starter
Locate your GraphQL controller, add an argument to that method of type HttpServletRequest - and then add headers to that as you prefer (see previous section on some more specific suggestions)

Jackson deserializer priority?

I have a Spring Boot app that is modeling ActityStreams objects and for the most part Jackson's Polymorphic Deserialization works well.
There are 'objects' in the JSON which are references (links) and not JSON objects with type information. For instance
"actor":"https://some.actors.href/ rather than
"actor":{
"type":"Actor",
"name":"SomeActor"
}
I've written custom deserializers and and placed them on the fields to deal with this
#JsonDeserialize (using = ActorOrLinkDeserializer.class)
private Actor actor;
However my ActorOrLinkDeserializer is instantiated but never called and Jackson complains with Missing type id when trying to resolve subtype of [simple type, class org.w3.activity.streams.Actor]: missing type id property 'type' (for POJO property 'actor') which is from the polymorphic deserializer.
It appears that the polymorphic deserialization code takes precedence over my local #JsonDeserialize annotation and I need a way to force my code to run first.
I've tried using my own ObjectMapper rather than Boot's and there's no difference.
I'd appreciate pointers and suggestions.
It turns-out there's a fairly simple solution to this problem using a DeserializationProblemHandler.
What I've implemented that works for all test cases so far is
1.
objectMapper.addHandler(new DeserProblemHandler());
or register with Spring Boot.
2.
public class DeserProblemHandler extends DeserializationProblemHandler {
public JavaType handleMissingTypeId(DeserializationContext ctxt, JavaType baseType, TypeIdResolver idResolver, String failureMsg) {
return TypeFactory.defaultInstance().constructType(baseType.getRawClass());
}
}
Add a constructor to each of the polymorphic classes that takes a string argument which is the href.

#RequestBody is getting null values

I have created a simple REST service (POST). But when i call this service from postman #RequestBody is not receiving any values.
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
#RestController
public class Add_Policy {
#ResponseBody
#RequestMapping(value = "/Add_Policy", headers = {
"content-type=application/json" }, consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST)
public Policy GetIPCountry( #RequestBody Policy policy) {
System.out.println("Check value: " + policy.getPolicyNumber());
return policy;
}
}
My java Bean object is like below:
public class Policy {
private String PolicyNumber;
private String Type;
private String Tenture;
private String SDate;
private String HName;
private String Age;
public String getPolicyNumber() {
return PolicyNumber;
}
public void setPolicyNumber(String policyNumber) {
PolicyNumber = policyNumber;
}
public String getType() {
return Type;
}
public void setType(String type) {
Type = type;
}
public String getTenture() {
return Tenture;
}
System.out.println is printing a null as a value for PolicyNumber.
Please help me to resolve this issue.
JSON which i am passing in request body is
{
"PolicyNumber": "123",
"Type": "Test",
"Tenture": "10",
"SDate": "10-July-2016",
"HName": "Test User",
"Age": "10"
}
I have even set Content-Type to application/json in postman
Check the #RequestBody import,
wrong that will cause the problem in most cases.
import io.swagger.v3.oas.annotations.parameters.RequestBody;
to solve problem It should be
import org.springframework.web.bind.annotation.RequestBody;
Try setting the first character of the properties in your JSON to lower case. Eg.
{
"policyNumber": "123",
"type": "Test",
"tenture": "10",
"sDate": "10-July-2016",
"hName": "Test User",
"age": "10"
}
Basically, Spring uses getter and setter to set the properties of the the bean object. And it takes the property of the JSON object, matches it with the setter of the same name. eg to set the policyNumber property it tries to find a setter with the name setpolicyNumber() in your bean class and use that to set the value of your bean object.
Setter would have been missed. So, Object values do not get set.
If you are not in power to change the JSON format and still want to fix this problem, try adding
#JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
annotation before your DTO (Policy in example) class.
Java convention demands the name of variable in a POJO (attribute of a class) must to be the first character in lowercase.
You have uppercase letters in your JSON properties, which is what is causing the failure.
I had lombok in my pom, and lombok annotations on my bean. I did not properly installed lombok with my STS yet, and had similar issue, my bean was not populated.
When I removed lombok annotations, my bean was properly populated.
Seems like a combination of lomboc not properly installed on STS + lomboc annotations on my bean.
if you are using Lombok Api then there are no Getters and Setters publicly visible or available to the #ResponseBody and #RequestBody annotation.
That is why we read the JSON request with null values.
So you need to comment those #Getter, #Setter annotation to Receive JSON response and Read JSON request object and generate the same getters and setters.
Restart or Hot Load (using Spring-Boot-Devtools) server and it should work for sure.
You can still use your lombok api for other Entities or BO's.
In my case was a Lombok issue. I removed all the lombok annotations and created the constructor, setter and getter manually.
As an advise, I would also set the JSON to lowercase to follow the convention.
Use the annotation org.springframework.web.bind.annotation.RequestBody and not org.springframework.web.bind.annotation.ResponseBody
In my case, empty constructor must be defined.
public MyClass(){}
Apart from lowerCamelCasing, for me what additionally needed was applying #JsonProperty(value="your expected JSON KEY name") for each of the getter and setter methods and using this operator under the POJO/Bean/DTO class.
Sample Code:
#JsonProperty(value="policyNumber")
public void setPolicyNumber(String policyNumber) {
this.policyNumber = policyNumber;
}
Had the same issue but for my case only one field was not being set. A log on the request body object showed it was being recieved as null. deleted getters and setters for the field and autogenerated them using the IDE and all worked fine.
I highly suspect a mismatch in the getter and setter definition can also cause this
I have been having this issue too, but the best way i solve mine was checking on spaces after the first quotes in every initialization of fields in my json values
see spring PropertyNamingStrategy(UPPER_CAMEL_CASE,LOWER_CAMEL_CASE ,LOWER_CASE
etc... defalult SNAKE_CASE).
Spring will auto change http contorller class parameter by naming strategy, which may be not consistant with your request json
take SNAKE_CASE as a ex, when "myToken" in java controller class, you client should send my_token instead of myToken
If you are using Lombok you need compileOnly and annotationProcessor
In my case I missed the 2nd one. So I got all null values
compileOnly 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
1-Make Entity class properties start with lowercase.
2-Check for Annotations.
3-Check for Constructor--> **Entity classes should have two constructor.
4-Check for Getter and Setters.
In my case, date format was given incorrectly

Resources