Filtering entity fields dynamically in Spring Data rest json Response - json

Hi I have a requirement to dynamically ignore entity fields in spring data rest response [I know they can be done in a static way by using #JsonIgnore annotation] ideally based on a spring security Role .The role part is still manageable but how to dynamically ignore fields in the json response is a challenge.
After some analysis and the docs I think jackson is the way to go as spring data rest does provide jackson customization via jackson modules and mixins http://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.custom-jackson-deserialization .
So I think in jackson api it could be done via #jsonFilter and then suppling the same when the ObjectMapper write the object [more details here http://www.cowtowncoder.com/blog/archives/2011/09/entry_461.html] .
But I am not sure how this could be wired up with Spring data rest (basically the part where I acan inject the filterprovider into spring data rest objectmapper).Let me know if anyone has tried this or someone from the Spring data team has insights .
Will post an answer myself If I am able to achieve the same.
UPDATE
So I figured out that the way to implement custom filtering is through the jackson BeanSerializerModifier .Got great help from #cowtowncoder on twitter .Also helpful reference or holy grails for filtering with jackson http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html

So yes finally I was able to solve this .The trick here is to use a custom BeanSerializerModifier and register it via a Custom Module [which is the custom hook available to customize spring data rest jackson serialization],something like
setSerializerModifier( new CustomSerializerModifier()).build()));
now you can customize our BeanSerializerModifier by overriding the method changeProperties to apply your custom filter ,which basically includes and excludes BeanPropertyWriter based on your logic .sample below
List<BeanPropertyWriter> included = Lists.newArrayList();
for (BeanPropertyWriter property : beanProperties)
if (!filter.contains(property.getName()))
included.add(property);
this way you can include any logic per class or otherwise and filter properties form response in a custom manner.Hope It Helps
Also have updated my code on github do look at https://github.com/gauravbrills/SpringPlayground

This example shows how to implement a dynamic JSON transformation (filtering) in a Spring Boot REST controller. It is using AOP controller advice to change controller method output in runtime. Code on github: https://github.com/andreygrigoriev/jsonfilter
AOP Advice
#ControllerAdvice
#SuppressWarnings("unused")
public class FilterAdvice implements ResponseBodyAdvice<Object> {
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
String fields = ((ServletServerHttpRequest) request).getServletRequest().getParameter("fields");
return new FilterMappingJacksonValue<>(body, StringUtils.isEmpty(fields) ? new String[] {} : fields.split(","));
}
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
}
FilterMappingJacksonValue
public class FilterMappingJacksonValue<T> extends MappingJacksonValue {
public FilterMappingJacksonValue(final T value, final String... filters) {
super(value);
setFilters(new SimpleFilterProvider().addFilter("dynamicFilter",
filters.length > 0 ? SimpleBeanPropertyFilter.filterOutAllExcept(filters) : SimpleBeanPropertyFilter.serializeAll()));
}
}
Simple DTO
#Data
#AllArgsConstructor
#JsonFilter("dynamicFilter")
public class Book {
String name;
String author;
}
BookController
#RestController
#SuppressWarnings("unused")
public class BookController {
#GetMapping("/books")
public List<Book> books() {
List<Book> books = new ArrayList<>();
books.add(new Book("Don Quixote", "Miguel de Cervantes"));
books.add(new Book("One Hundred Years of Solitude", "Gabriel Garcia Marquez"));
return books;
}
}

Related

jackson-dataformat-xml turns #ResponseBody to XML

So I had a perfectly working Spring app. Most of my controller methods are for ajax calls that return JSON via #ResponseBody with the jackson api and returns my Java POJO to JSON.
I have a need to turn XML to JSON, so I find that Jackson has a tool for that, and I add this to my POM to use the library:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.0</version>
</dependency>
So that I may use this:
XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(sb.toString().getBytes());
But now the #ResponseBody is returning XML and not JSON. I Remove the dependency and the controllers return JSON again.
Any way to get both? I want the xmlMapper, and JSON from the response body.
jackson-dataformat-xml appears to be registering a MappingJackson2HttpMessageConverter with a XmlMapper, along with other HttpMessageConverters that work with XML. If you always intended to return JSON from your controllers, you can change what HttpMessageConverter your app uses by overriding configureMessageConverters
For Spring 5.0 and above,
#Configuration
public class HttpResponseConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> supportsXml(converter) || hasXmlMapper(converter));
}
private boolean supportsXml(HttpMessageConverter<?> converter) {
return converter.getSupportedMediaTypes().stream()
.map(MimeType::getSubtype)
.anyMatch(subType -> subType.equalsIgnoreCase("xml"));
}
private boolean hasXmlMapper(HttpMessageConverter<?> converter) {
return converter instanceof MappingJackson2HttpMessageConverter
&& ((MappingJackson2HttpMessageConverter)converter).getObjectMapper().getClass().equals(XmlMapper.class);
}
}
For older versions of Spring, replace implements WebMvcConfigurer with extends WebMvcConfigurerAdapter
Add Accept: application/json to HTTP request header.
Read this for an analysis of how Spring does content negotiation and allows producing either XML or JSON.
The simplest way is to add an extension at the URL: Instead of /path/resource use /path/resource.json
You may also add a format parameter e.g. /path/resource?format=json or pass an appropriate Accept header
In my case, the XmlMapper was actually inserted into the application context as an #Bean. The other solutions here did not work for me. It seems like one of those issues where context matters, so for people coming here from a different context than the other answerers, here's another angle: I had to insert my own ObjectMapper.
#Configuration
public class XmlMapperConfiguration {
#Bean // me, culprit
public XmlMapper xmlMapper() {
return new XmlMapper();
}
#Bean // to make sure the rest of the application still works with JSON
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
I'm going to throw in an #Primary on the ObjectMapper one. It seems suspicious that Spring would even choose that one consistently. Since XmlMapper extends ObjectMapper, why would it not take that one, so #Primary won't hurt.

How to read path variables or URL parameters in Custom deserializer Spring Boot

I've written a custom deserializer for an entity in Spring Boot application. Now I need to access URL parameters and path variables in my custom deserializer for some data manipulation. Please tell me how can i do that.
Thanks
For path variables deserialization you don't need to involve jackson but you have to "tune" Spring MVC itself by means of defining your own org.springframework.core.convert.converter.Converter
For example:
#Component
public class StringToLocalDateTimeConverter
implements Converter<String, LocalDateTime> {
#Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(
source, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
#GetMapping("/findbydate/{date}")
public GenericEntity findByDate(#PathVariable("date") LocalDateTime date) {
return ...;
}
Here is an article about it.
1. HandlerMethodArgumentResolver
See spring-mvc-custom-data-binder, Another answer
2. HandlerInterceptor
See Interception

How can I wrap a JSON response in Spring

Suppose I have two sets of controllers in Spring:
/jsonapi1/*
/jsonapi2/*
both of which return objects that are to be interpretted as JSON text.
I'd like some kind of filter to wrap the responses from one set of these controllers so that:
the original response is contained within another object.
For example, if /jsonapi1/count returns:
{"num_humans":123, "num_androids":456}
then the response should be wrapped and returned as follows:
{ "status":0,
"content":{"num_humans":123, "num_androids":456}
}
if an exception happens in the controller, then filter should catch the exception and report it as follows
{ "status":5,
"content":"Something terrible happened"
}
The responses from the other controllers are returned unchanged.
We're currently customizing a MappingJackson2HttpMessageConverter passed to WebMvcConfigurerAdapter.configureMessageConverters in order to perform the above tasks. Works great except that it doesn't seem possible for this approach to be selective about the URLs (or controller classes) it applies to.
Is it possible to apply these kinds of wrappers to individual controller classes or URLs?
Update: Servlet filters look like a solution. Is it possible chose which filter gets applied to which controller methods, or which URLs?
I was struggling on this for multiple days. The solution by #Misha didn't work for me. I was able to finally get this working using ControllerAdvice and ResponseBodyAdvice.
ResponseBodyAdvice allows to inject custom transformation logic on the response returned by a controller but before it is converted to HttpResponse and committed.
This is how my controller method looks:
#RequestMapping("/global/hallOfFame")
public List<HallOfFame> getAllHallOfFame() {
return hallOfFameService.getAllHallOfFame();
}
Now i wanted to add some standard fields around the response like devmessage and usermessage. That logic goes into the ResponseAdvice:
#ControllerAdvice
public class TLResponseAdvice implements ResponseBodyAdvice<Object> {
#Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
#Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
// TODO Auto-generated method stub
final RestResponse<Object> output = new RestResponse<>();
output.setData(body);
output.setDevMessage("ResponseAdviceDevMessage");
output.setHttpcode(200);
output.setStatus("Success");
output.setUserMessage("ResponseAdviceUserMessage");
return output;
}
}
The entity classes look like this:
#Setter // All lombok annotations
#Getter
#ToString
public class RestResponse<T> {
private String status;
private int httpcode;
private String devMessage;
private String userMessage;
private T data;
}
#Entity
#Data // Lombok
public class HallOfFame {
#Id
private String id;
private String name;
}
To handle exceptions, simply create another ControllerAdvice with ExceptionHandler. Use the example in this link.
Advantages of this solution:
It keeps your controllers clean. You can support any return type from your controller methods.
Your controller return type class does not need to extend some base class as required by the AOP approach.
You do not need to hack your way through Spring filters by using HttpServletResponseWrappers. They come up with a performance penalty.
EDIT - 17th September 2019
To handle exceptions use #ExceptionHandler. Refer code below.
#ExceptionHandler(Exception.class)
#ResponseBody
public MyResponseEntity<Object> handleControllerException(HttpServletRequest request, Throwable ex) {
// default value
int httpCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
if(ex instanceof ResourceNotFoundException) {
httpCode = HttpStatus.NOT_FOUND.value();
}
...
}
The way I understand your question, you have exactly three choices.
Option #1
Manually wrap your objects in simple SuccessResponse, ErrorResponse, SomethingSortOfWrongResponse, etc. objects that have the fields you require. At this point, you have per-request flexibility, changing the fields on one of the response wrappers is trivial, and the only true drawback is code repetition if many of the controller's request methods can and should be grouped together.
Option #2
As you mentioned, and filter could be designed to do the dirty work, but be wary that Spring filters will NOT give you access to request or response data. Here's an example of what it might look like:
#Component
public class ResponseWrappingFilter extends GenericFilterBean {
#Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain) {
// Perform the rest of the chain, populating the response.
chain.doFilter(request, response);
// No way to read the body from the response here. getBody() doesn't exist.
response.setBody(new ResponseWrapper(response.getStatus(), response.getBody());
}
}
If you find a way to set the body in that filter, then yes, you could easily wrap it up. Otherwise, this option is a dead end.
Option #3
A-ha. So you got this far. Code duplication is not an option, but you insist on wrapping responses from your controller methods. I'd like to introduce the true solution - aspect-oriented programming (AOP), which Spring supports fondly.
If you're not familiar with AOP, the premise is as follows: you define an expression that matches (like a regular expression matches) points in the code. These points are called join points, while the expressions that match them are called pointcuts. You can then opt to execute additional, arbitrary code, called advice, when any pointcut or combination of pointcuts are matched. An object that defines pointcuts and advice is called an aspect.
It's great for expressing yourself more fluently in Java. The only drawback is weaker static type checking. Without further ado, here's your response-wrapping in aspect-oriented programming:
#Aspect
#Component
public class ResponseWrappingAspect {
#Pointcut("within(#org.springframework.stereotype.Controller *)")
public void anyControllerPointcut() {}
#Pointcut("execution(* *(..))")
public void anyMethodPointcut() {}
#AfterReturning(
value = "anyControllerPointcut() && anyMethodPointcut()",
returning = "response")
public Object wrapResponse(Object response) {
// Do whatever logic needs to be done to wrap it correctly.
return new ResponseWrapper(response);
}
#AfterThrowing(
value = "anyControllerPointcut() && anyMethodPointcut()",
throwing = "cause")
public Object wrapException(Exception cause) {
// Do whatever logic needs to be done to wrap it correctly.
return new ErrorResponseWrapper(cause);
}
}
The final result will be the non-repeating response wrapping that you seek. If you only want some or one controller receive this effect, then update the pointcut to match methods only within instances of that controller (rather than any class holding the #Controller annotation).
You'll need to include some AOP dependencies, add the AOP-enabling annotation in a configuration class, and make sure something component-scans the package this class is in.
Simplest way i manage custom responses from controllers is by utilising the Map variable.
so your code ends up looking like:
public #ResponseBody Map controllerName(...) {
Map mapA = new HashMap();
mapA.put("status", "5");
mapA.put("content", "something went south");
return mapA;
}
beauty of is is that you can configure it any thousand ways.
Currently i use for object transmition, custom exception handling and data reporting, too easy.
Hope this helps
I am also using AOP with #Around. Developed a custom annotation and using that for point cut. I am using a global Response. It has the status, Message and data which is of type List of type
List <? extends parent> dataList
( which can solve your class cast exception). All the entities extends this Parent class. This way I can set all the data into my List.
Also I am using the message key as param with the custom annotation and setting it in action.
Hope this helps.

Is #XmlElement necessary for Jaxb

My question is, whether it is necessary to add #XmlElement before each element in your pojo to be picked up by jaxb, when making a JSON response. I am using jersey-json 1.17 . The reason I ask this is because, the example given on Jersey site does not use the annotation.
I get an out put as {}, but when I add #XmlElement before the attributes, I get the expected JSON output. Am I doing something wrong, because of which my JSON string is empty ?
My code :
The vertices list is populated in the constructor.
This produces the wrong output of {}
#XmlRootElement
public class SquareModel {
List<Float> vertices = new ArrayList<Float>();
....
}
Whereas this produces the a correct JSON string :
#XmlRootElement
public class SquareModel {
#XmlElement(name="vertices")
List<Float> vertices = new ArrayList<Float>();
....
}
My resource class which returns the JSON
#GET
#Produces(MediaType.APPLICATION_JSON)
public SquareModel getJsonString() {
return new SquareModel();
}
Thanks :)
No, by default a JAXB (JSR-22#) implementation will treat all public fields and properties (get/set combinations) as mapped (not requiring the #XmlElement annotation).
http://blog.bdoughan.com/2012/07/jaxb-no-annotations-required.html
If you wish to annotate a field I would recommend annotating your class with #XmlAccessorType(XmlAccessType.FIELD)
http://blog.bdoughan.com/2011/06/using-jaxbs-xmlaccessortype-to.html
According to this http://jersey.java.net/nonav/documentation/latest/json.html#json.jaxb.approach.section
You should have this annotation (I'm also using it in my code, even though it XML oriented, but it gives me cool JSON also)
Taking this approach will save you a lot of time, if you want to
easily produce/consume both JSON and XML data format. Because even
then you will still be able to use a unified Java model. Another
advantage is simplicity of working with such a model, as JAXB
leverages annotated POJOs and these could be handled as simple Java
beans.
A disadvantage of JAXB based approach could be if you need to work
with a very specific JSON format. Then it could be difficult to find a
proper way to get such a format produced and consumed. This is a
reason why a lot of configuration options are provided, so that you
can control how things get serialized out and deserialized back.
Following is a very simple example of how a JAXB bean could look like.
Example 5.3. Simple JAXB bean implementation
#XmlRootElement
public class MyJaxbBean {
public String name;
public int age;
public MyJaxbBean() {} // JAXB needs this
public MyJaxbBean(String name, int age) {
this.name = name;
this.age = age;
}
}

Can I configure Jackson JSON pretty printing from annotations or from Spring MVC controller?

I'm using Jackson 1.9.6 (codehaus) for JSON serialization of my response bodies in a Spring MVC application, and I'm having trouble finding a way to configure pretty printing. All of the code examples I've been able to find (like this and this) involve playing with an instantiation of ObjectMapper or ObjectWriter, but I don't currently use an instantiation of these for anything else. I wouldn't even know where to put this code. All of my Jackson configurations are taken care of by annotating the POJOs being serialized to JSON.
Is there a way to specify pretty printing in an annotation? I would think they would have put that in #JsonSerialize, but it doesn't look like it.
My class to be serialized looks like this:
#JsonAutoDetect
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class JSONObject implements Serializable{...}
and my Spring controller method looks like this:
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody List<Object> getMessagesAndUpdates(HttpServletRequest request, HttpServletResponse response) {
JSONObject jsonResponse = new JSONObject();
.
.
.
//this will generate a non-pretty-printed json response. I want it to be pretty-printed.
return jsonResponse;
}
I searched and searched for something similar and the closest I could find was adding this bean to my Application context configuration (NOTE: I am using Spring Boot so I am not 100% certain this will work as-is in a non-Spring Boot app):
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder()
{
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true);
return builder;
}
In my opinion, its the cleanest available solution and works pretty well.
Adding this as a separate answer so I can format the output.
As luck would have it, the non-Spring Boot solution wasn't too far from the Spring Boot solution :)
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}