How to exclude certain modelAttributes in JSON output (using ContentNegotiatingViewResolver) - json

I have a Spring MVC 3.1.0 project and I have configured a ContentNegotiatingViewResolver bean to automatically generate JSON output for a given endpoint (which uses org.springframework.web.servlet.view.json.MappingJacksonJsonView).
I have a few controller methods which add data to the JSP page (via model.addAttribute("foo", fooService.getFoo());) that I don't want to appear in the JSON output.
I have tried adding a #JsonIgnore annotation to my service method getFoo() (which returns a Map<String, String>) but it doesn't work. I still see the foo object being marshalled in my JSON output when I hit that controller.
Can anyone suggest another way of achieving this or tell me why the JsonIgnore annotation is not working?

MappingJacksonJsonView serializes all the contents of the model into a json - all the objects that you have placed in your model object, so it does not matter if you have marked one of the service methods with #JsonIgnore, as long it ends up in the model which it does because of the call to model.addAttribute("foo".. it would get serialized. The fix could be simply to not add the model attribute, or to use #ResponseBody which will give you control over the specific response object that is being serialized.
Another option is to specify the exact keys that you will be using when configuring MappingJacksonJsonView:
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" name="jsonView">
<property name="modelKeys">
<set>
<value>model1</value>
<value>model2</value>
</set>
</property>
</bean>

Extend MappingJackson2JsonView class and override filterMap(Map model) method.
In the filterMap method filter out keys with the name of modelAttributes you need to exclude.
public class MappingJackson2JsonViewExt extends MappingJackson2JsonView {
private static final Set<String> EXCLUDED_KEYS = new HashSet<>();
public static void excludeModelKey(final String key) {
EXCLUDED_KEYS.add(key);
}
#Override
protected Object filterModel(final Map<String, Object> model) {
final Map<String, Object> filteredModel = model.entrySet().stream()
.filter(e -> {
final String key = e.getKey();
return !EXCLUDED_KEYS.contains(key);
})
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
return super.filterModel(filteredModel);
}
}

Related

spring boot return escaped json

my code
#GetMapping(value = {"/metadata"}, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public String getMetadata() {
return dppService.getMetadata();
}
the method getMetadata will just return a json string. it just read data from the json file, and it is in another library can not be changed.
But when call this api, i got the follow reponse:
"{\"Namespace\":\"com.xxx\"...
the json string was escaped.
expected:
"{"Namespace":"com.xxx"...
How could i make it return the right json? BTW, our other services also return a json string in the controller, but their response will not be escaped which is so confused for me.
You could do this two ways:
From what I could understand you are having this issues because you might be returning the json as a string from from the service method dppService.getMetadata() by converting it manually to a string. If so , change that and instead return a POJO class from the service method as well as the controller, spring default jackson converter should automatically convert it to a json when the request is served. (I would suggest you go with this approach)
Another approach (the hacky less desirable one) if you still want to keep returning a string then you could configure the StringMessageConverter like below to accept json:
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(
Charset.forName("UTF-8"));
stringConverter.setSupportedMediaTypes(Arrays.asList( //
MediaType.TEXT_PLAIN, //
MediaType.TEXT_HTML, //
MediaType.APPLICATION_JSON));
converters.add(stringConverter);
}
root cause:
There is a configuration file in the project:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(jacksonBuilder().build()));
converters.stream()
.filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
.findFirst()
.ifPresent(converter -> ((MappingJackson2HttpMessageConverter) converter).setDefaultCharset(UTF_8));
}
This configuration overrite the defualt jackson behavior. There are two ways to solve this issue:
1.Remove this configuration, then it will be the default behavior
2.Add the StringHttpMessageConverter in this configuration, see Ananthapadmanabhan's option2

How to make Enunciate show my data type as structured JSON (instead of as "custom")?

I have this simple service which echoes an ID parameter wrapped in a JSON object:
#Path("job")
public class JobResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("{id}")
public Job readById(#PathParam("id") long id) {
Job j = new Job();
j.id = id;
return j;
}
}
The return value's Job class is declared as:
public class Job {
public long id;
}
The documentation generated with Enunciate shows the service's data type as "custom". Is it possible to have Enunciate spit out a more detailed explanation of the return type, for example a JSON representation?
If you change the return type to javax.ws.rs.core.Response the documentation should then show the data type as JSON.
You would need to slightly modify your method as:
...
return Response.status(Status.OK).entity(j).build();
Add annotation to the Job class
#javax.xml.bind.annotation.XmlRootElement
Without this annotation the enunciate will display the DTO as "custom" or "file"
I got exactly the same problem with a simple REST Jersey webservice.
Here are the annotations of My returned object (no more):
#XmlRootElement(name = "OReponseInitialiser")
#XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
#XmlType (name="OReponseInitialiser")
public class OReponseInitialiser
And the webservice declaration:
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
#Path ("initialiser")
public OReponseInitialiser initialiser(#Context HttpServletRequest pRequete, ...) throws OException
I build the documentation with the ant task
<enunciate
basedir="${projet.repertoire}/src"
buildDir="${enunciate.working.dir}"
javacSourceVersion="1.8"
javacTargetVersion="1.8"
configFile="${projet.repertoire}/build_enunciate.xml"
>
<include name="**/*.java"/>
<classpath refid="compile.classpath"/>
<export artifactId="docs" destination="${docs.dir}"/>
</enunciate>

Filtering entity fields dynamically in Spring Data rest json Response

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;
}
}

How to parse json arrary?

I have come across a problem of parsing json data . I am building project using spring boot based on REST api . When i have to parse data corresponding to domain then it is very easy , i use RequestBody in controller method with domain name but in current scenerio i have a list of domain in json form :
{
"data":[
{
"type":"abc",
"subtypes":[
{
"leftValue":"BEACH",
"rightValue":"MOUNTAIN",
"preferencePoint":60
},
{
"leftValue":"ADVENTURE",
"rightValue":"LEISURE",
"preferencePoint":60
}
]
},
{
"type":"mno",
"subtypes":[
{
"leftValue":"LUXURY",
"rightValue":"FUNCTIONAL",
"preferencePoint":60
},
{
"leftValue":"SENSIBLE",
"rightValue":"AGGRESIVE",
"preferencePoint":0
}
]
}
]
}
I am sending data in list where type is the property of class Type
and class Type has list of Subtypes class and subtype class contains leftValue and rightValue as enums
I am using spring boot which uses jackson liberary by default and i want to parse this data into corresponding Type class using Jackson. Can any one provide me solution.
It wasn't clear to me if you have static or dynamic payload.
Static payload
For static one, I would personally try to simplify your payload structure. But your structure would look like this. (I skipped getters and setters. You can generate them via Lombok library).
public class Subtype{
private String leftValue;
private String rightValue;
private int preferencePoint;
}
public class Type{
private String type;
private List<Subtype> subtypes;
}
public class Data{
private List<Type> data;
}
Then in your controller you inject Data type as #RequestBody.
Dynamic payload
For dynamic payload, there is option to inject LinkedHashMap<String, Object> as #RequestBody. Where value in that map is of type Object, which can be casted into another LinkedHashMap<String, Object> and therefore this approach support also nested objects. This can support infinite nesting this way. The only downside is that you need to cast Objects into correct types based on key from the map.
BTW, with pure Spring or Spring Boot I was always able to avoid explicit call against Jackson API, therefore I don't recommend to go down that path.

Using Spring's #RequestBody and reading HttpServletRequest.getInputStream() afterwards

I'm mapping my request's JSON POST data into an object using Spring's #RequestBody annotation and MappingJacksonHttpMessageConverter. However after that I'd like to read the data in String form to do some additional authentication. But when the marshalling has happened, the InputStream in HttpServletRequest is empty. Once I remove the #RequestBody parameter from the method the reading of POST data into a String works as expected.
Do I have to compromise by giving up the #RequestBody and doing the binding somehow manually or is there a more elegant solution?
So, basically you need to compute a hash of the request body. The elegant way to do it is to apply a decorator to the InputStream.
For example, inside a handler method (in this case you can't use #RequestBody and need to create HttpMessageConverter manually):
#RequestMapping(...)
public void handle(HttpServletRequest request) throws IOException {
final HashingInputStreamDecorator d =
new HashingInputStreamDecorator(request.getInputStream(), secretKey);
HttpServletRequest wrapper = new HttpServletRequestWrapper(request) {
#Override
public ServletInputStream getInputStream() throws IOException {
return d;
}
};
HttpMessageConverter conv = ...;
Foo requestBody = (Foo) conv.read(Foo.class, new ServletServerHttpRequest(wrapper));
String hash = d.getHash();
...
}
where hash is computed incrementally in overriden read methods of HashingInputStreamDecorator.
You can also use #RequestBody if you create a Filter to apply the decorator. In this case decorator can pass the computed hash to the handler method as a request attribute. However, you need to map this filter carefully to apply it only to the requests to specific handler method.
In your urlMapping bean you can declare list of additional interceptors:
<bean id="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<bean class="org.foo.MyAuthInterceptor"/>
</list>
</property>
</bean>
Those interceptors have access to HttpServletRequest, though if you read from the stream the chances are that parameter mapper won't be able to read it.
public class AuthInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mav) {
...
}
}
If I understand this correctly, one common way used with JAX-RS (which is somewhat similar to Spring MVC with respect to binding requests) is to first "bind" into some intermediate raw type (usually byte[], but String also works), and manually bind from that to object, using underlying data binder (Jackson). I often do this to be able to fully customize error handling of data binding.