How #JsonInclude(Include.NON_NULL) works In Spring Boot - java

I am trying to understand what is the reason of existence #JsonInclude to use it in my DTOs. Let's look at this simple example:
A Code:
class DemoApplication {
static void main(String[] args) {
SpringApplication.run DemoApplication, args
}
#PostMapping("/")
String greet(#RequestBody Greeting greeting) {
return "Hello ${greeting.name}, with email ${greeting.email}"
}
}
#JsonInclude(JsonInclude.Include.NON_NULL)
class Greeting {
String name
String email
}
B Code:
class DemoApplication {
static void main(String[] args) {
SpringApplication.run DemoApplication, args
}
#PostMapping("/")
String greet(#RequestBody Greeting greeting) {
return "Hello ${greeting.name}, with email ${greeting.email}"
}
}
class Greeting {
String name
String email
}
The only difference between the A code and the B code is that in the B code the greeting class does not use the #JsonInclude annotation.
But if I do some simple CURL requests to that endpoint (A code and B code):
~ curl -H "Content-Type: application/json" -X POST localhost:8080
{"timestamp":"2018-04-22T21:18:39.849+0000","status":400,"error":"Bad Request","message":"Required request body is missing: public java.lang.String com.example.demo.DemoApplication.greet(com.example.demo.Greeting)","path":"/"}
~ curl -H "Content-Type: application/json" -X POST localhost:8080 -d '{}'
Hello null, with email null
~ curl -H "Content-Type: application/json" -X POST localhost:8080 -d '{"name": "AlejoDev"}'
Hello AlejoDev, with email null
~ curl -H "Content-Type: application/json" -X POST localhost:8080 -d '{"name": "AlejoDev", "email":"info#alejodev.com"}'
Hello AlejoDev, with email info#alejodev.com
Get the same behavior, then what is the utility of using #JsonInclude annotation?
For example, I expected that in A code when I sent a request with only one field, like this:
~ curl -H "Content-Type: application/json" -X POST localhost:8080 -d '{"name": "AlejoDev"}'
the Greeting object became an object with only one field, and not an object with two fields. one full and one empty.

What that annotation means is that when an object is deserialized whether to include only non-null fields on not.
When you are using it for an object that you are getting as an input, even if the input fields are null, because the variables name and email are class level fields, they will be inited to their default value which is null.
So when you create your return statement you are always going to get null for those.
Try this annotation with a JSON object as a response and then try populating some fields and keeping some fields null and then see the response to various calls. This will help you understand the effects of this annotation.

#JsonInclude(Include.NON_NULL) or #JsonInclude(JsonInclude.Include.NON_NULL)
is used to ignore null fields in an object.
In your particular example you have returned a String value that is why it is printing null.
If you try to return the complete object, then you will find that the null fields are not included in the response body

Related

spring-boot custom annotation for validating headers

I am using spring-boot-1.5.10 and I am using spring-security in my application. I would like to create a custom annotation and which should using securityContext holder...Let me elaborate my question with sample code.
curl -X GET -H "roles: READ" -H "Content-Type: application/json" -H
"Accept: application/json" -H "app-name: sample" -H "app-id: sample"
-H "customer-id: 123" -H "market: EN" -H "country-code: EN" -H "Accept-Language: application/json" -H "Cache-Control: no-cache"
"http://localhost:9992/api/v1/apps"
Controller
#GetMapping("/apps")
#PreAuthorize("hasAnyAuthority('ROLE_READ', 'ROLE_WRITE')")
public ResponseEntity<List<Apps>> getApps(#AuthenticationPrincipal AppAuthentication appAuthentication) {
if(appAuthentication.isGrantedAnyOf("ROLE_READ") && isBlank(appAuthentication.getAppContext().customerId())) {
throw new IllegalArgumentException("Missing header customerId");
}
if(appAuthentication.isGrantedAnyOf("ROLE_WRITE") && isBlank(appAuthentication.getAppContext().customerId()) && isBlank(appAuthentication.getAppContext().appId())) {
throw new IllegalArgumentException("Missing header customerId & AppId");
}
//write business logic here
}
spring-security preAuthorize will only check if the roles are allowed.Also, I can enhance the preAuthorize annotation but it's common for many microservice and also i don't have permission to touch security realm. so, I would like to create a custom annotation. we should configure the roles & headers to validate for the particular roles. Like below
#GetMapping("/apps")
#PreAuthorize("hasAnyAuthority('ROLE_READ', 'ROLE_WRITE')")
#ValidateHeaders("role=ROLE_READ",value={"customerId","app-id"})
public ResponseEntity<List<Apps>> getApps(#AuthenticationPrincipal AppAuthentication appAuthentication) {
//write business logic here
}
Any hint would be really appreciable.
Disclamer - I was working with spring boot 2, so not everything might be applicable for you
This is a stripped down version of something I've implemented a while back.
I suggest implementing enums for roles and values.
You can't do redirects from #before anotations so you have to throw an exception and catch it with a global ex handler, redirect from there.
Consider also adding to the annotation optional fields - in case of multiple roles, match all or one,
redirect path, exception type to invoke in case of no access. Then in the exception handler you can do redirects depending on which exception was invoked. Since you are doing redirects from gloabl ex handler, if you add a redirect path, you'll have to bundle it with the exception you are throwing, meaning you'll need custom exceptions.
Annotation
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface ValidateHeaders {
Roles[] roles();
String[] values();
}
Aspect Class
#Aspect
#Component
#RequiredArgsConstructor(onConstructor = #__(#Autowired)) //Autowired annotated lombok generated constructor
public class ValidateHeadersAspect {
private final #NonNull HttpServletRequest request; //Inject request to have header access
private final #NonNull UserService userService;//Your user service here
//Aspect can be placed on clas or method
#Before("within(#com.org.package.ValidateHeaders *) || #annotation(com.org.package.ValidateHeaders)")
public void validateAspect(JoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
HasAccess validateHeaders = method.getAnnotation(ValidateHeaders.class);
if(validateHeaders == null) { //If null it was a class level annotation
Class annotatedClass = joinPoint.getSignature().getDeclaringType();
validateHeaders = (ValidateHeaders)annotatedClass.getAnnotation(ValidateHeaders.class);
}
Roles[] roles = validateHeaders.roles(); //Roles listed in annotation
String[] values = validateHeaders.values(); //Values listed in
//Validate request here ... determine isAuthorised
if( !isAuthorized ){
throw new HeaderAuthrizationException()
}
}
}
Exception Handler
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(HeaderAuthrizationException.class)
public RedirectView HeaderAuthrizationException(HeaderAuthrizationException ex) {
return new RedirectView("/redirect");
}
}

MethodArgumentTypeMismatchException calling a Rest method with curl

I have a basic SpringBoot 2.0.6.RELEASE app. Using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR with a restful architecture
#PutMapping(path = "/users/alarms2", consumes = "application/json", produces = "application/json")
public void setAlerts2(#RequestHeader(value = "Authorization") String authHeader,
#RequestBody AlertConfiguration alertConfiguration)
throws DataAccessException, ClientProtocolException, SQLException, IOException {
..
}
but when I call this method from curl:
curl -X PUT -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJyaWNhcmQub2SAsZUBnbWFpbC5jb20iLCJleHAiOjE2MDAxODMzNDAsImlhdCI6MTUzOTcwMzM0MH0.2gbyyGnkcoHjOw7HbUBBQgb59Bw8iAyFbqTe2DPUlOA-V5UwrW3KXWHZlXssZni8oRJ_o1QRzAFtAWMfz7f0Yw" -d ‘{“symbol": “MENU”, "alarmKey”:”VEGAN” , "enabled": "true"}' "http://95.90.225.68:1133/restos/api/v1/users/alarms2"
I got this error in the server:
2018-10-17 17:16 [http-nio-1133-exec-9] WARN o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver.resolveException(140) - Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'long'; nested exception is java.lang.NumberFormatException: For input string: "alarms2"]
You probably have another method with a more general signature that overlaps given one. Something like:
#PutMapping(path = "/users/{userId}", ...)
public void someMethod(#PathVariable(value = "userId") Long userId) {
...
}
So when you consume /api/v1/users/alarms2 Spring first tries to convert "alarms2" (which is obviously not a valid Long) to userId (which is Long)

Jersey reading only first #QueryParam out of #BeanParam parameters

I have this Jersey POST resource :
#POST
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
public Response blablabla(InputStream inputStream,
#BeanParam ImportParams importParams) throws IOException, JAXBException, SAXException {
Here is the ImportParams class :
public class ImportParams {
#QueryParam(value = "importType")
public ImportType importType = ImportType.MERGE;
#ApiParam("Force stop point type for all stop points in file. Useful if no modality defined in the netex file.")
#QueryParam(value = "forceStopType")
public StopTypeEnumeration forceStopType;
}
When I use curl to post to the the resource, only the first query parameter I specify after the question mark in the URL is read by jersey :
curl -XPOST -H"Content-Type: application/xml" -H"authorization: bearer $TOKEN" -d#$3 http://localhost:8585/services/stop_places/netex?forceStopType=TRAM_STATION&importType=MERGE
==> forceStopType has the right value, and importType is null
curl -XPOST -H"Content-Type: application/xml" -H"authorization: bearer $TOKEN" -d#$3 http://localhost:8585/services/stop_places/netex?importType=MERGE&forceStopType=TRAM_STATION
==> importType has the right value and forceStopType is null
I've used #BeanParam many times before and it used to work, so I must be missing something obvious.... Thanks for your help
Haha found out - stupid me. Had to put double quotes around the URL when cURLing :
curl -XPOST -H"Content-Type: application/xml" -H"authorization: bearer $TOKEN" -d#$3 "http://localhost:8585/services/stop_places/netex?forceStopType=TRAM_STATION&importType=MERGE"

Grails fail to receive serialized data

How do I send data (JSON or XML) and deserialize it in grails controller?
I tried render params and render request.JSON whitch returned me empty json, and also the command approach
import grails.converters.JSON
class TestController {
def test(RestCommand r){
render r as JSON
}
}
class RestCommand {
String data
static constraints = {
data nullable: false
}
}
witch resulted in
{"data":null,"errors":{"errors":[{"object":"x.x.RestCommand","field":"data","rejected-value":null,"message":"Property [data] of class [class x.x.RestCommand] cannot be null"}]}}
here's my curl request:
curl -X POST --header "Content-Type:application/json" --data '{"id":1}' localhost:8080/myApp/myController/myMethod
P.S. I have viewed similar questions in stackexchange, but as I've discussed above, none of the approaches worked.
Grails provides serialized object for you, your data just has to have the same fields as command.
This works for me:
import grails.converters.JSON
class TestController {
def test(RestCommand r){
if(r.hasErrors()) {
render (
status: 400,
text: r.getErrors() as JSON)
} else {
render "id: "+r.id+" data: "+r.someData
}
}
}
class RestCommand {
Integer id
String someData
static constraints = {
id nullable: false
}
}
Here's the request:
curl -X POST --header "Content-Type:application/json" --data '{"id":1,"someData":"here you go some data"}' localhost:8080/myapp/test/test
results:
id: 1 data: here you go some data
They way you are doing it is working (sending JSON to a Controller and deserializing it). If you send JSON to this action, the command will bind the received data (like it does in your example). Your problem is that the data in your curl test is invalid. Normally, you would check if the Command has errors like :
def test(RestCommand r){
if(r.hasErrors()) {
// handle error
response.status = 400
render "error"
return
}
render "ok"
}

BadRequestException in JAX-RS endpoint

I have the following endpoint declared in my JAX-RS application:
#WebService
public interface AlertWeb
{
#POST
#Path("/add")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public StringResponse addAlert(String name,
int amount, String timespan, String repo, String action);
}
I'm using the following curl command to call this endpoint:
curl -X POST -H "Cache-Control: no-cache"
-H "Content-Type: application/x-www-form-urlencoded"
-d "name=yellow&amount=2&timespan=DAY&repo=A&action=%7Baction%3A'GreenDivAction'%2C+message%3A'Simple+Message'%2C+args%3A+%5B'arg1'%2C'arg2'%5D%7D"
http://localhost:8080/AdminService/alert/add
but keep getting the following error when I make the request:
javax.ws.rs.BadRequestException: java.lang.NumberFormatException: For input string: ""
Note Line breaks in curl syntax added for readability.
What am I doing wrong?
You will need to add #FormParam to your method parameters if you want them to be injected as such
#POST
#Path("/add")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response addAlert(
#FormParam("name") String name,
#FormParam("amount") int amount,
#FormParam("timespan") String timespan,
#FormParam("repo") String repo,
#FormParam("action") String action) {
}

Categories

Resources