Spring MVC Ignore Json property for a given controller method - java

I have a Java class (MyResponse) that is returned by a multiple RestController methods and has a lot of fields.
#RequestMapping(value = "offering", method=RequestMethod.POST)
public ResponseEntity<MyResponse> postOffering(...) {}
#RequestMapping(value = "someOtherMethod", method=RequestMethod.POST)
public ResponseEntity<MyResponse> someOtherMethod(...) {}
I want to ignore (e.g. not serialize it) one of the properties for just one method.
I don't want to ignore null fields for the class, because it may have a side effect on other fields.
#JsonInclude(Include.NON_NULL)
public class MyResponse { ... }
The JsonView looks good, but as far as I understand I have to annotate all other fields in the class with a #JsonView except the one that I want to ignore which sounds clumsy. If there is a way to do something like "reverse JsonView" it will be great.
Any ideas on how to ignore a property for a controller method?

Props to this guy.
By default (and in Spring Boot) MapperFeature.DEFAULT_VIEW_INCLUSION is enabled in Jackson. That means that all fields are included by default.
But if you annotate any field with a view that is different than the one on the controller method this field will be ignored.
public class View {
public interface Default{}
public interface Ignore{}
}
#JsonView(View.Default.class) //this method will ignore fields that are not annotated with View.Default
#RequestMapping(value = "offering", method=RequestMethod.POST)
public ResponseEntity<MyResponse> postOffering(...) {}
//this method will serialize all fields
#RequestMapping(value = "someOtherMethod", method=RequestMethod.POST)
public ResponseEntity<MyResponse> someOtherMethod(...) {}
public class MyResponse {
#JsonView(View.Ignore.class)
private String filed1;
private String field2;
}

Related

Is it possible to map a nested mapping in spring-mvc to a global path?

I have something like this:
#RestController
#RequestMapping("/{id}")
public class MyController {
#GetMapping
public String get(#PathVariable String id) {
...
}
#PostMapping
public String post(#PathVariable String id, Payload payload) {
...
}
#GetMapping("/deeper/{id}")
public String getDeeper(#PathVariable String id) {
....
}
}
This gives 3 mappings:
/{id} (GET)
/{id} (POST)
/{id}/deeper/{id} (GET)
I would like the third of them to be just /deeper/{id} (GET).
Is it possible to do this leaving the method in the same controller and leaving that controller-wise #RequestMapping annotation?
What you request is not possible because you cannot avoid a requestMapping on a class level which makes no sense because being on class level means that you want that path to affect to all your methods.
Keep in mind that a RestController is RESTful and a class level requestMapping is used to avoid adding the same resource path to every method, so it does not make sense to have a method that can't fit in within that resource (you should move it to another controller instead).
This being said, there are a few things you can try:
1 This is not recommended. Use more than one path value on your class #ResquestMapping, in your case:
#RestController
#RequestMapping("/{id}", "/")
public class MyController{...}
You can kinda achieve what you want with this but this is extremely
discouraged and a code smell because basically means that all your methods will accept url paths either starting with id or with /, think carefully if you want to use this approach.
2 The recommended one, Remove the #RequestMapping in the class level and just update the path on every method, in your case:
#RestController
public class MyController {
#GetMapping(value = /{id})
public String get(#PathVariable String id) {
...
}
#PostMapping(value = "/{id}")
public String post(#PathVariable String id, Payload payload) {
...
}
#GetMapping("/deeper/{id}")
public String getDeeper(#PathVariable String id) {
....
}
}
3 also a recommended one Just move the method that does not fit in your controller "general logic" to another controller class which make's sense since that method is not affected by the controller general logic.

Spring Data Rest / Spring Hateoas Custom Controller - PersistentEntityResourceAssembler

I'm attempting to add some additional business logic to the auto-generated endpoints from the RepositoryRestResource. Please see the code below:
Resource:
#RepositoryRestResource(collectionResourceRel="event", path="event")
public interface EventRepository extends PagingAndSortingRepository<Event, Long> {
}
Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private PagedResourcesAssembler<Event> pagedResourcesAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<PersistentEntityResource> getEvents(Pageable pageable,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Page<Event> events = eventRepository.findAll(pageable);
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
}
}
I've looked at the following two stackoverflow articles:
Can I make a custom controller mirror the formatting of Spring-Data-Rest / Spring-Hateoas generated classes?
Enable HAL serialization in Spring Boot for custom controller method
I feel like I am close, but the problem that I am facing is that:
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
returns an error saying:
"The method toResource(Page<Event>, Link) in the type PagedResourcesAssembler<Event> is not applicable
for the arguments (Page<Event>, PersistentEntityResourceAssembler)".
The toResource method has a method signature that accepts a ResourceAssembler, but I'm not sure how to properly implement this and I can't find any documentation on the matter.
Thanks in advance,
- Brian
Edit
My issue was that I thought I could override the controller methods that are auto-created from #RepositoryRestResource annotation without having to create my own resource and resource assembler. After creating the resource and resource assembler I was able to add my business logic to the endpoint.
Resource:
public class EventResource extends ResourceSupport {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Resource Assembler:
#Component
public class EventResourceAssembler extends ResourceAssemblerSupport<Event, EventResource> {
public EventResourceAssembler() {
super(EventController.class, EventResource.class);
}
#Override
public EventResource toResource(Event entity) {
EventResource eventResource = createResourceWithId(entity.getId(), entity);
eventResource.setName(entity.getName());
return eventResource;
}
}
Updated Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private EventResourceAssembler eventResourceAssembler;
#Autowired
private PagedResourcesAssembler<Event> pageAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<EventResource> getEvents(Pageable pageable) {
Page<Event> events = eventRepository.findAll(pageable);
// business logic
return pageAssembler.toResource(events, eventResourceAssembler);
}
}
The thing I don't like about this is that it seems to defeat the purpose of having a RepositoryRestResource. The other approach would be to use event handlers that would get called before and/or after the create, save, delete operations.
#RepositoryEventHandler(Event.class)
public class EventRepositoryEventHandler {
#HandleBeforeCreate
private void handleEventCreate(Event event) {
System.out.println("1");
}
}
There doesn't seem to be any events for the findAll or findOne operations. Anyways, both these approaches seem to solve my problem of extending the auto generated controller methods from RepositoryRestResource.
It requires a PagedResourcesAssembler, Spring will inject one for you if you ask.
public PagedResources<Foo> get(Pageable page, PagedResourcesAssembler<Foo> assembler) {
// ...
}
In this case the resource is Foo. It seems in your case the resource you're trying to return is an Event. If that's so, I would expect your code to look something like:
private ResourceAssembler<Event> eventAssembler = ...;
public PagedResources<Event> get(Pageable page, PagedResourcesAssembler<Event> pageAssembler) {
Event event = ...;
return eventAssembler.toResource(event, pageAssembler);
}
You provide the ResourceAssembler<Event> that tells Spring how to turn Event into a Resource. Spring injects the PagedResourcesAssembler<Event> into your controller method to handle the pagination links. Combine them by calling toResource and passing in the injected pageAssembler.
The final result can be returned simply as a body as above. You could also use things like HttpEntity to gain more control over status codes and headers.
Note: The ResourceAssembler you provide can literally be something as simple as wrapping the resource, such as Event, with a Resource object. Generally you'll want to add any relevant links though.
To hack it you can use just PagedResourcesAssembler<Object> like:
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedModel<PersistentEntityResource> getEvents(
Pageable pageable,
PersistentEntityResourceAssembler persistentAssembler,
PagedResourcesAssembler<Object> pageableAssembler
) {
return pageableAssembler.toModel(
(Page<Object>) repository.findAll(pageable),
persistentAssembler
);
}

#JsonView doesn't work for me (Spring 4.1.5, Jackson 2.5.1)

I have a field in my Entity with #JsonView annotation:
#JsonView(View.Secure.class)
private String password;
Inside my controller:
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
#JsonView(View.Secure.class)
public ResponseEntity<?> getAllUsers(){
return createUserListResponse();
}
My View class:
public class View {
public static class Secure {}
}
I've expected that response will contain only "password" field, but instead it contains nothing. When i remove annotation #JsonView(View.Secure.class) from Controller - it works as usual and returns all fields. What am i doing wrong? Is it required to add some additional configuration into Spring config?
I used this tutorial: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring

Call method of Controller1 into another method of Controller2 in Spring

I did Google a lot to find my problem but I couldn't and sorry If this question already on the stack overflow because I have not find it.
First let take a look into the code
#Controller
public class Controller1 {
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView methodHandler(Parameters) {
}
public int calculation(int i){
//Some Calcucation
return i;
}
}
and second controller is
#Controller
public class Controller2 {
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView methodHandler(Parameters) {
//In this I want to call the calculation(1) method of controller1.
}
}
My question is that is there any way to call the method of calculation() of controler1 in to controller2. But remember I don't want to make method static in controller1.Is there anyway to call it without make it static?
Thanks
Yasir
You should create service bean for example in configuration file (or use # one of the annotaions) and inject it into controller. For example ()
#Configuration
public class MyConfig {
#Bean
public MyService myService(){
return new MyService();
}
}
#Controller
public class Controller1 {
#Autowire
private MyService myService;
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView First(Parameters) {
myService.calculation();
}
}
#Controller
public class Controller2 {
#Autowire
private MyBean myBean;
#RequestMapping(value = "URL", method = RequestMethod.GET)
public ModelAndView First(Parameters) {
myService.calculation();
}
}
Your controllers should not call each other. If there is a logic which needs to be used by both controllers, it is much better to put that into separate bean, which will be used by both controllers. Then you can simply inject that bean to whicheveer controller neccessary. Try not to put any business logic to controllers, try tu put it to specialized class instead which will be web independent if possible and will accept web agnostic business data as user email, account number etc. No http request or response. This way your class with actual logic is reusable and can be unit tested much more easily. Also, if there is state, it should be contained in your classes outside controllers. Controllers should be stateless and not contail any state at all.
When using MVC pattern and you are deciding where to put your logic, you should separate business logic into model and into controllers you should put only logic regarding user interaction, as explained in this stack overflow post.

Spring restful webservice returning JSON

I just took the tutorial over at Spring.io http://spring.io/guides/gs/rest-service/ and created a simple rest service. But, does anybody know how I can return multiple objects in JSON format? If I for instance have a person class with a name and an id, how can I add three persons to /persons?
You can use the #ResponseBody annotation and just return whatever you want, providing that those objects can be jsonized.
For example, you can have a bean like this:
#Data
public class SomePojo {
private String someProp;
private List<String> someListOfProps;
}
and then in your controller you can have:
#ResponseBody
#RequestMapping("/someRequestMapping")
public List<SomePojo> getSomePojos(){
return Arrays.<String>asList(new SomePojo("someProp", Arrays.<String>asList("prop1", "prop2"));
}
and Spring by default would use its Jackson mapper to do it, so you'd get a response like:
[{"someProp":"someProp", "someListOfProps": ["prop1", "prop2"]}]
The same way, you can bind to some objects, but this time, using the #RequestBody annotation, where jackson would be used this time to pre-convert the json for you.
what you can do is
#RequestMapping("/someOtherRequestMapping")
public void doStuff(#RequestBody List<SomePojo> somePojos) {
//do stuff with the pojos
}
Try returning a list from the method:
#RequestMapping("/greetings")
public #ResponseBody List<Greeting> greetings(
#RequestParam(value="name", required=false, defaultValue="World") String name) {
return Arrays.asList(new Greeting(counter.incrementAndGet(),String.format(template, name)));
}

Categories