Better way to use json mixin with spring 4 controller - json

Is there a better way (possibly using annotations) to add a mixin to a spring controller.
Currently I am doing this:
#RequestMapping(value = "/accounts",
produces = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.GET,
params = "q")
#ResponseBody
#ResponseStatus(value = HttpStatus.OK)
public final String getAccountsViaQuery(#RequestParam("q") final String query)
throws JsonGenerationException, JsonMappingException, IOException {
final List<Account> matchingAccounts = accountService.findByAccountNameOrNumber(query);
ObjectMapper mapper = new ObjectMapper();
SerializationConfig serializationConfig = mapper.getSerializationConfig();
serializationConfig.addMixInAnnotations(Account.class, Account.SearchJsonMixin.class);
return mapper.writeValueAsString(matchingAccounts);
}
I would rather do this
#RequestMapping(value = "/accounts",
produces = MediaType.APPLICATION_JSON_VALUE,
method = RequestMethod.GET,
params = "q")
#ResponseBody
#ResponseStatus(value = HttpStatus.OK)
public final List<Account> getAccountsViaQuery(#RequestParam("q") final String query)
throws JsonGenerationException, JsonMappingException, IOException {
return accountService.findByAccountNameOrNumber(query);
}
Looks much better to me, no boilerplate code and the return type is compiler checked.
Is is possible to use some sort of annotation on my controller to add a json mixin?

Wrote an annotation to solve this issue.
https://github.com/jackmatt2/JsonResponse

Related

Spring boot application POST request not works and not update the database

I have java Spring Boot application. When I’m sending data POST request through POSTman. JSONObject cannot be returned, POSTman showing {"false"}
This is my controller,
package com.lagoma.demo.controller;
#RestController
#RequestMapping(value = "/user")
public class User {
#Autowired
private UserService userService;
#GetMapping
public List<UserModel> getUsers() {
return userService.getUsers();
}
#RequestMapping(method = RequestMethod.GET, value = "/get")
public UserModel getOneUser(#RequestParam(value = "id", required = false, defaultValue = "00") int id) {
return userService.getUser(id);
}
#RequestMapping(method = RequestMethod.POST, value = "/save")
public boolean updateUser(#RequestBody UserModel userModel){
return userService.updateUser(userModel);
}
Spring Controller can't return primitive type or its wrapper .
You need to return some object.
If you dont have any object in scope, returning a Map will also do.
Change your updateUser to something like this
#RequestMapping(method = RequestMethod.POST, value = "/save")
public Map<String, Boolean> updateUser(#RequestBody UserModel userModel){
return Collections.singletonMap("result", userService.updateUser(userModel));
}
or with a Object
#RequestMapping(method = RequestMethod.POST, value = "/save")
public User updateUser(#RequestBody UserModel userModel){
// assuming userService.updateUser will return User object
User user = userService.updateUser(userModel);
return user;
}

Spring Boot: Return a empty JSON instead of empty body when returned object is null

I have a RestController and when I call the method:
#RequestMapping(value = "/sigla/{sigla}")
#ResponseBody
public PaisDTO obterPorSigla(#PathVariable String sigla) {
return service.obterPorSigla(sigla);
}
If a record is found, I get a good JSON response:
{"nome":"Brasil","sigla":"BR","quantidadeEstados":27}
but when nothing is found on database the RestController returns null and I get a empty response, completely blank body.
How can I display a empty JSON instead of a blank response? Like bellow:
{}
Complete Controller:
#RestController
#RequestMapping("/pais")
public class PaisController {
#Autowired
private PaisService service;
#RequestMapping
public ResponseEntity<List<PaisDTO>> obterTodos() {
return CreateResponseEntity.getResponseEntity(service.obterTodos());
}
#RequestMapping(value = "/sigla/{sigla}", method = RequestMethod.GET, consumes="application/json", produces="application/json")
public ResponseEntity<PaisDTO> obterPorSigla(#PathVariable String sigla) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
PaisDTO paisDTO = service.obterPorSigla(sigla);
if(paisDTO != null) return new ResponseEntity<PaisDTO>(paisDTO, headers, HttpStatus.OK);
else return new ResponseEntity<PaisDTO>(headers, HttpStatus.OK);
}
Solution 1:
You have to implement you entity class with Serializable
Solution 2:
Your class should have getter and setter
In my case the getter and setter were given protected access modifiers. so I changed them to public and vola it worked
First, if you're using #RestController annotation you don't need the #ResponseBody annotation, get rid of that.
Second if you're trying to have REST Controller, then you're missing a few things, do it like this:
#RequestMapping(value = "/sigla/{sigla}", method = RequestMethod.GET, consumes = "application/json", produces="application/json")
public ResponseEntity<PaisDTO> obterPorSigla(#PathVariable String sigla) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
PaisDTO paisDTO = service.obterPorSigla(sigla);
if(paisDTO != null) return new ResponseEntity<>(paisDTO, headers, HttpStatus.OK);
else return new ResponseEntity<>(headers, HttpStatus.OK);
}
In the example above if you'll get null then you'll return an empty response JSON.
The only way that I could find was to create an empty class
#JsonSerialize
public class EmptyJsonBody {
}
Then add this to your response
#PostMapping(value = "/sigla/{sigla}")
public ResponseEntity obterPorSigla(#PathVariable String sigla) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
PaisDTO paisDTO = service.obterPorSigla(sigla);
ResponseEntity.BodyBuilder responseBuilder = ResponseEntity.ok().headers(headers);
if(paisDTO != null) {
return responseBuilder.body(paisDTO);
} else {
return responseBuilder.body(new EmptyJsonBody());
}
}

How to get JSON response from controller to another controller in Spring

I have one controller in an app that returns JSON data, like so:
#RequestMapping(value = "/{number}", method = RequestMethod.GET)
#ResponseBody
public String number(
HttpServletRequest request,
HttpServletResponse response,
#PathVariable int number
) {
JSONObject dataObject = new JSONObject();
dataObject.put("firstName", "Sheelten");
dataObject.put("lastName", "Pestay");
JSONArray data = new JSONArray();
data.put(dataObject);
return data.toString();
}
I have another controller on a different app, that I want to receive the JSON data, like so:
#RequestMapping(
value = "/data/test/",
method = RequestMethod.GET
)
#ResponseBody
public String testService(
HttpServletRequest request,
HttpServletResponse response,
Model model
) {
return response.toString();
}
I'm not really sure how I would go about receiving the JSON data into my testService controller method. I've tried googling and using the response object with no luck.
Anyone have an idea how I'd do this?
Found an answer, in case anyone else is ever looking for a solution to this.
If you are using Java with Spring, use the RestTemplate class. See below:
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://yoururl/here", String.class);
The string result will be your JSON string.

How to include only specific properties when serializing with Jackson

I am trying to implement a universal method which serializes the given object to JSON, but only those properties which are passed in a collection. If possible I want to get this functionality without specifying #JsonFilter on the class. For this I am trying to use FilterExceptFilter from Jackson 2.4.1. Dependencies:
jackson-core-2.4.1.jar
jackson-databind-2.4.1.jar
jackson-annotations-2.4.0.jar
Here is what I have at the moment:
public static String serializeOnlyGivenFields(Object o,
Collection<String> fields) throws JsonProcessingException {
if ((fields == null) || fields.isEmpty()) return null;
Set<String> properties = new HashSet<String>(fields);
SimpleBeanPropertyFilter filter =
new SimpleBeanPropertyFilter.FilterExceptFilter(properties);
SimpleFilterProvider fProvider = new SimpleFilterProvider();
fProvider.addFilter("fieldFilter", filter);
fProvider.setDefaultFilter(filter);
ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(fProvider);
String json = mapper.writeValueAsString(o);
return json;
}
However, the filter is never applied. It always serializes all properties.
Set<String> fields = new HashSet<String>(); fields.add("name");
String json = Serializer.serializeOnlyGivenFields(e, fields);
System.out.println(json);
{"name":"Test entity","description":"Test description"}
I have also tried to register the FilterProvider on the ObjectWriter, but same result:
String json = mapper.writer(fProvider).writeValueAsString(o);
What am I missing? Is there a nice way to achieve this with Jackson?
Based on http://www.cowtowncoder.com/blog/archives/2011/09/entry_461.html an alternate way to set up the filter is setting up a class that extends JacksonAnnotationIntrospector and overrides findFilterId. You can then specify to find your filter in the findFilterId. This could be made to be as robust if you want based on some other map or algorithm. Below is sample code. Not sure if the performance is better than the solution above but it seems to be simpler and probably more easily extensible. I was doing this for serializing CSV using Jackson. Any feedback is welcome!
public class JSON {
private static String FILTER_NAME = "fieldFilter";
public static String serializeOnlyGivenFields(Object o,
Collection<String> fields) throws JsonProcessingException {
if ((fields == null) || fields.isEmpty()) fields = new HashSet<String>();
Set<String> properties = new HashSet<String>(fields);
SimpleBeanPropertyFilter filter =
new SimpleBeanPropertyFilter.FilterExceptFilter(properties);
SimpleFilterProvider fProvider = new SimpleFilterProvider();
fProvider.addFilter(FILTER_NAME, filter);
ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector( new AnnotationIntrospector() );
String json = mapper.writer(fProvider).writeValueAsString(o);
return json;
}
private static class AnnotationIntrospector extends JacksonAnnotationIntrospector {
#Override
public Object findFilterId(Annotated a) {
return FILTER_NAME;
}
}
}
One additional thing is that you have to indicate Java classes for which filter is to be used by #JsonFilter annotation:
#JsonFilter("fieldFilter")
public class MyType { }
and then it should apply.
I have found a solution based on Jackson: How to add custom property to the JSON without modifying the POJO. I override BeanSerializer#serializeFields to always use BeanSerializer#serializeFieldsFiltered instead. This way the filter is always applied.
Performance-wise not a very good solution, since an ObjectMapper has to be constructed at every method call. Feel free to post improvements or suggestions!
Module implementation:
public class FilteredModule extends SimpleModule {
private static final long serialVersionUID = 1L;
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new BeanSerializerModifier() {
#Override
public JsonSerializer<?> modifySerializer(
SerializationConfig config,
BeanDescription beanDesc,
JsonSerializer<?> serializer) {
if (serializer instanceof BeanSerializerBase) {
return new FilteredBeanSerializer(
(BeanSerializerBase) serializer);
}
return serializer;
}
});
}
private class FilteredBeanSerializer extends BeanSerializer {
public FilteredBeanSerializer(BeanSerializerBase source) {
super(source);
}
#Override
protected void serializeFields(Object arg0, JsonGenerator arg1,
SerializerProvider arg2) throws IOException,
JsonGenerationException {
super.serializeFieldsFiltered(arg0, arg1, arg2);
}
}
}
API method:
public static String serializeOnlyGivenFields(Object o,
Collection<String> fields) throws JsonProcessingException {
if ((fields == null) || fields.isEmpty()) fields = new HashSet<String>();
Set<String> properties = new HashSet<String>(fields);
SimpleBeanPropertyFilter filter =
new SimpleBeanPropertyFilter.FilterExceptFilter(properties);
SimpleFilterProvider fProvider = new SimpleFilterProvider();
fProvider.addFilter("fieldFilter", filter);
fProvider.setDefaultFilter(filter);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new FilteredModule());
String json = mapper.writer(fProvider).writeValueAsString(o);
return json;
}
Example
Entity e = new Entity("Test entity", "Test description");
Set<String> fields = new HashSet<String>(); fields.add("name");
String json = JSON.serializeOnlyGivenFields(e, fields);
System.out.println(json);
{"name":"Test entity"}
Benchmark: 1000 iterations on the same object
serializeOnlyGivenFields: 536 ms
serialize (reuses ObjectMapper): 23 ms

Spring MVC Controller adds request object to response

I am building a JSON REST service with Spring 3.0.5 and my response contains the object from my request although I did not add it. I am using the MappingJacksonJsonView and Jackson 1.6.4 for rendering the ModelAndView object to JSON.
The User object is simple
public class SimpleUser {
private String username;
private String password;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password;
}
}
One of the requests looks like this
#RequestMapping(value = "/register", method = RequestMethod.GET)
public ModelAndView register(SimpleUser user) {
ModelAndView mav = new ModelAndView();
mav.addObject("ok", "success");
return mav;
}
Then I call the service with
curl 'http://localhost:8080/register?username=mike&password=mike'
The response I expect is
{"ok": "success"}
The response I get is
{"ok":"success","simpleUser":{"username":"mike","password":"mike"}}
Where and why is the user object added to the ModelAndView and how can I prevent that?
Possible solution
One way to work around this is to use Model instead of SimpleUser. This seems to work but it should be possible to use the business object.
This works:
#RequestMapping(value = "/register", method = RequestMethod.GET)
public ModelAndView register(Model model) {
log.debug("register(%s,%s)", model.asMap().get("usernmae"), model.asMap().get("password"));
ModelAndView mav = new ModelAndView();
mav.addObject("ok", "success");
return mav;
}
It looks like you're trying to process a form submission and retrieve the result via ajax. If this is the case, you don't want to return a ModelAndView object. Use the #ResponseBody annotation to have Jackson represent your return object as a json object.
public #ResponseBody Map registerUser(SimpleUser user){
Map responseMap = new HashMap();
if(registerUser(user)){
responseMap.put("OK", "Success");
} else {
responseMap.put("OK", "Failure");
}
return responseMap;
}
For Spring 3.1.x You can set the modelKey property in org.springframework.web.servlet.view.json.MappingJacksonJsonView in your *servlet.xml like below:
Servlet.xml:
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="modelKey" value="appResponse"/>
</bean>
Request Method:
#RequestMapping(value="/access")
public #ResponseBody Model getAccess(Model model) {
...
model.addAttribute("appResponse", responseDetails);
...
return model;
}
When you set a specific modelKey, all other attributes attached the the model will be ignored, hence the form parameters/request parameters. In additional, this provides a clearer design if your are presenting views for multiple media types (application/xml or application/json).