Can one use the Jackson #JsonView and #JsonFilter annotations to modify the JSON returned by a Spring MVC controller, whilst using MappingJacksonHttpMessageConverterand Spring's #ResponseBody and #RequestBody annotations?
public class Product
{
private Integer id;
private Set<ProductDescription> descriptions;
private BigDecimal price;
...
}
public class ProductDescription
{
private Integer id;
private Language language;
private String name;
private String summary;
private String lifeStory;
...
}
When the client requests a collection of Products, I'd like to return a minimal version of each ProductDescription, perhaps just its ID. Then in a subsequent call the client can use this ID to ask for a full instance of ProductDescription with all properties present.
It would be ideal to be able to specify this on the Spring MVC controller methods, as the method invoked defines the context in which client was requesting the data.
This issue is solved!
Follow this
Add support for Jackson serialization views
Spring MVC now supports Jackon's serialization views for rendering
different subsets of the same POJO from different controller
methods (e.g. detailed page vs summary view).
Issue: SPR-7156
This is the SPR-7156.
Status: Resolved
Description
Jackson's JSONView annotation allows the developer to control which aspects of a method are serialiazed. With the current implementation, the Jackson view writer must be used but then the content type is not available. It would be better if as part of the RequestBody annotation, a JSONView could be specified.
Available on Spring ver >= 4.1
UPDATE
Follow this link. Explains with an example the #JsonView annotation.
Ultimately, we want to use notation similar to what StaxMan showed for JAX-RS. Unfortunately, Spring doesn't support this out of the box, so we have to do it ourselves.
This is my solution, it's not very pretty, but it does the job.
#JsonView(ViewId.class)
#RequestMapping(value="get", method=RequestMethod.GET) // Spring controller annotation
public Pojo getPojo(#RequestValue Long id)
{
return new Pojo(id);
}
public class JsonViewAwareJsonView extends MappingJacksonJsonView {
private ObjectMapper objectMapper = new ObjectMapper();
private boolean prefixJson = false;
private JsonEncoding encoding = JsonEncoding.UTF8;
#Override
public void setPrefixJson(boolean prefixJson) {
super.setPrefixJson(prefixJson);
this.prefixJson = prefixJson;
}
#Override
public void setEncoding(JsonEncoding encoding) {
super.setEncoding(encoding);
this.encoding = encoding;
}
#Override
public void setObjectMapper(ObjectMapper objectMapper) {
super.setObjectMapper(objectMapper);
this.objectMapper = objectMapper;
}
#Override
protected void renderMergedOutputModel(Map<String, Object> model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
Class<?> jsonView = null;
if(model.containsKey("json.JsonView")){
Class<?>[] allJsonViews = (Class<?>[]) model.remove("json.JsonView");
if(allJsonViews.length == 1)
jsonView = allJsonViews[0];
}
Object value = filterModel(model);
JsonGenerator generator =
this.objectMapper.getJsonFactory().createJsonGenerator(response.getOutputStream(), this.encoding);
if (this.prefixJson) {
generator.writeRaw("{} && ");
}
if(jsonView != null){
SerializationConfig config = this.objectMapper.getSerializationConfig();
config = config.withView(jsonView);
this.objectMapper.writeValue(generator, value, config);
}
else
this.objectMapper.writeValue(generator, value);
}
}
public class JsonViewInterceptor extends HandlerInterceptorAdapter
{
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
JsonView jsonViewAnnotation = handlerMethod.getMethodAnnotation(JsonView.class);
if(jsonViewAnnotation != null)
modelAndView.addObject("json.JsonView", jsonViewAnnotation.value());
}
}
In spring-servlet.xml
<bean name="ViewResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
</map>
</property>
<property name="defaultContentType" value="application/json" />
<property name="defaultViews">
<list>
<bean class="com.mycompany.myproject.JsonViewAwareJsonView">
</bean>
</list>
</property>
</bean>
and
<mvc:interceptors>
<bean class="com.mycompany.myproject.JsonViewInterceptor" />
</mvc:interceptors>
I don't know how things work with Spring (sorry!), but Jackson 1.9 can use #JsonView annotation from JAX-RS methods, so you can do:
#JsonView(ViewId.class)
#GET // and other JAX-RS annotations
public Pojo resourceMethod()
{
return new Pojo();
}
and Jackson will use View identified by ViewId.class as the active view. Perhaps Spring has (or will have) similar capability? With JAX-RS this is handled by standard JacksonJaxrsProvider, for what that's worth.
Looking for the same answer I came up with an idea to wrap ResponseBody object with a view.
Piece of controller class:
#RequestMapping(value="/{id}", headers="Accept=application/json", method= RequestMethod.GET)
public #ResponseBody ResponseBodyWrapper getCompany(HttpServletResponse response, #PathVariable Long id){
ResponseBodyWrapper responseBody = new ResponseBodyWrapper(companyService.get(id),Views.Owner.class);
return responseBody;
}
public class ResponseBodyWrapper {
private Object object;
private Class<?> view;
public ResponseBodyWrapper(Object object, Class<?> view) {
this.object = object;
this.view = view;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
#JsonIgnore
public Class<?> getView() {
return view;
}
#JsonIgnore
public void setView(Class<?> view) {
this.view = view;
}
}
Then I override writeInternal method form MappingJackson2HttpMessageConverter to check if object to serialize is instanceof wrapper, if so I serialize object with required view.
public class CustomMappingJackson2 extends MappingJackson2HttpMessageConverter {
private ObjectMapper objectMapper = new ObjectMapper();
private boolean prefixJson;
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator jsonGenerator =
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
try {
if (this.prefixJson) {
jsonGenerator.writeRaw("{} && ");
}
if(object instanceof ResponseBodyWrapper){
ResponseBodyWrapper responseBody = (ResponseBodyWrapper) object;
this.objectMapper.writerWithView(responseBody.getView()).writeValue(jsonGenerator, responseBody.getObject());
}else{
this.objectMapper.writeValue(jsonGenerator, object);
}
}
catch (IOException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
this.objectMapper = objectMapper;
super.setObjectMapper(objectMapper);
}
public ObjectMapper getObjectMapper() {
return this.objectMapper;
}
public void setPrefixJson(boolean prefixJson) {
this.prefixJson = prefixJson;
super.setPrefixJson(prefixJson);
}
}
The answer to this after a many head banging dead ends and nerd rage tantrums is....
so simple. In this use case we have a Customer bean with a complex object Address embedded within it and we want to prevent the serialization of a property name surburb and street in address, when the json serialization take place.
We do this by applying an annotation #JsonIgnoreProperties({"suburb"}) on the field address in the Customer class, the number of fields to be ignored is limitless. e.g i want to ingnore both suburb and street. I would annotate the address field with #JsonIgnoreProperties({"suburb", "street"})
By doing all this we can create HATEOAS type architecture.
Below is the full code
Customer.java
public class Customer {
private int id;
private String email;
private String name;
#JsonIgnoreProperties({"suburb", "street"})
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Address.java
public class Address {
private String street;
private String suburb;
private String Link link;
public Link getLink() {
return link;
}
public void setLink(Link link) {
this.link = link;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getSuburb() {
return suburb;
}
public void setSuburb(String suburb) {
this.suburb = suburb;
}
}
In addition to #user356083 I've made some modifications to make this example work when a #ResponseBody is returned. It's a bit of a hack using ThreadLocal but Spring doesn't seem to provide the necessary context to do this the nice way.
public class ViewThread {
private static final ThreadLocal<Class<?>[]> viewThread = new ThreadLocal<Class<?>[]>();
private static final Log log = LogFactory.getLog(SocialRequestUtils.class);
public static void setKey(Class<?>[] key){
viewThread.set(key);
}
public static Class<?>[] getKey(){
if(viewThread.get() == null)
log.error("Missing threadLocale variable");
return viewThread.get();
}
}
public class JsonViewInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
JsonView jsonViewAnnotation = handlerMethod
.getMethodAnnotation(JsonView.class);
if (jsonViewAnnotation != null)
ViewThread.setKey(jsonViewAnnotation.value());
return true;
}
}
public class MappingJackson2HttpMessageConverter extends
AbstractHttpMessageConverter<Object> {
#Override
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
JsonGenerator jsonGenerator =
this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
// This is a workaround for the fact JsonGenerators created by ObjectMapper#getJsonFactory
// do not have ObjectMapper serialization features applied.
// See https://github.com/FasterXML/jackson-databind/issues/12
if (objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
jsonGenerator.useDefaultPrettyPrinter();
}
//A bit of a hack.
Class<?>[] jsonViews = ViewThread.getKey();
ObjectWriter writer = null;
if(jsonViews != null){
writer = this.objectMapper.writerWithView(jsonViews[0]);
}else{
writer = this.objectMapper.writer();
}
try {
if (this.prefixJson) {
jsonGenerator.writeRaw("{} && ");
}
writer.writeValue(jsonGenerator, object);
}
catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
Even better, since 4.2.0.Release, you can do this simply as follows:
#RequestMapping(method = RequestMethod.POST, value = "/springjsonfilter")
public #ResponseBody MappingJacksonValue byJsonFilter(...) {
MappingJacksonValue jacksonValue = new MappingJacksonValue(responseObj);
jacksonValue.setFilters(customFilterObj);
return jacksonValue;
}
References:
1. https://jira.spring.io/browse/SPR-12586
2. http://wiki.fasterxml.com/JacksonFeatureJsonFilter
Related
Currently facing an issue where I would like to alter the response of jax-rs resources based on some information passed into the response. The change that need to be made is the format of some of the json. Currently I am registering an ObjectMapper using jax #Provider and ContextResolver. However the getContext() method is only invoked a single time for each resource class. It is not possible to alter this based on every request.
Is it possible to inject or access the ObjectMapper in a ContainerResponseFilter?
#Provider
public class ObjectMapperResolver implements ContextResolver<ObjectMapper> {
#Context
private HttpServletRequest httpRequest;
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new DateDeserializer("some data format"));
mapper.registerModule(module);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return getMapperBasedOnRequest();
}
private ObjectMapper getMapperBasedOnRequest() {
if (true) {
return mapper;
} else {
return //something else;
}
}
private boolean containsLegacyHeader() {
return //get some information from the request headers or body
}
}
I decided to solve this issue by using a Filter which would be invoked based on a parameter provided in the request, in this case a header.
public class DemoFilter implements ContainerResponseFilter {
private final String UTC = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
private final String OTHER = "yyyy-MM-dd";
public static final String DATE_UTC = "utc";
private String getMapperBasedOnRequest(ContainerRequestContext requestContext) {
if (checkHeader(requestContext)) {
return OTHER;
} else {
return UTC;
}
}
private boolean checkHeader(ContainerRequestContext requestContext) {
return //check header
}
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
ObjectWriterInjector.set(new DateMod(getMapperBasedOnRequest(requestContext)));
}
public static class DateMod extends ObjectWriterModifier {
private String df;
public DateMod(String df) {
this.df = df;
}
#Override
public ObjectWriter modify(EndpointConfigBase<?> endpointConfigBase, MultivaluedMap<String, Object> multivaluedMap, Object o, ObjectWriter objectWriter, JsonGenerator jsonGenerator) throws IOException {
return objectWriter.with(new SimpleDateFormat(dateFormat));
}
}
}
I'm using Jackson with Spring. I have a few methods like this:
#RequestMapping(value = "/myURL", method = RequestMethod.GET)
public #ResponseBody Foo getFoo() {
// get foo
return foo;
}
The class Foo that's serialized is quite big and has many members. The serialization is ok, using annotation or custom serializer.
The only thing I can't figure out is how to define the naming convention. I would like to use snake_case for all the serializations.
So how do I define globally the naming convention for the serialization?
If it's not possible, then a local solution will have to do then.
Not sure how to do this globally but here's a way to do it at the JSON object level and not per each individual property:
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Foo {
private String myBeanName;
//...
}
would yield json:
{
"my_bean_name": "Sth"
//...
}
Actually, there was a really simple answer:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder b = new Jackson2ObjectMapperBuilder();
b.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
return b;
}
I added it in my main like so:
#SpringBootApplication
public class Application {
public static void main(String [] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder b = new Jackson2ObjectMapperBuilder();
b.propertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
return b;
}
}
The mapper has a setter for PropertyNamingStrategy (Method for setting custom property naming strategy to use.)
Look how it works in the tes example:
#Test
public void namingStrategy() throws Exception {
final ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String s) {
return s.toUpperCase();
}
});
final String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(new SomePojo("uuid_1", "user_1", "Bruce", "W.", 51));
System.out.println(json);
}
public static class SomePojo {
private String someIdAttachedToIt;
private String username;
private String fistName;
private String lastName;
private int age;
public SomePojo(String someIdAttachedToIt, String username, String fistName, String lastName, int age) {
this.someIdAttachedToIt = someIdAttachedToIt;
this.username = username;
this.fistName = fistName;
this.lastName = lastName;
this.age = age;
}
public String getSomeIdAttachedToIt() {
return someIdAttachedToIt;
}
public String getUsername() {
return username;
}
public String getFistName() {
return fistName;
}
public String getLastName() {
return lastName;
}
public int getAge() {
return age;
}
}
Output:
{
"SOMEIDATTACHEDTOIT" : "uuid_1",
"USERNAME" : "user_1",
"FISTNAME" : "Bruce",
"LASTNAME" : "W.",
"AGE" : 51
}
Provided strategies (I use LOWERCASE for the examples)
PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
PropertyNamingStrategy.SNAKE_CASE
To add your strategy globally in Spring, you can do it at least in 2 ways:
with a mapper module declared as a bean containing the naming strategy
with a custom object mapper configured as you want
Short version:
#Configuration
public static class Config {
#Bean
public Module module() {
return new SimpleModule() {
#Override
protected SimpleModule setNamingStrategy(PropertyNamingStrategy naming) {
super.setNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String propertyName) {
// example: "propertyName" -> "PROPERTYNAME"
return propertyName.toUpperCase();
}
});
return this;
}
};
}
}
Long version:
To declare the bean for the jackson module:
// config auto scan by spring
#Configuration
public static class ConfigurationClass {
// declare the module as a bean
#Bean
public Module myJsonModule() {
return new MySimpleModule();
}
}
// jackson mapper module to customize mapping
private static class MySimpleModule extends SimpleModule {
#Override
protected SimpleModule setNamingStrategy(PropertyNamingStrategy naming) {
return super.setNamingStrategy(new MyNameStrategy());
}
}
// your naming strategy
private static class MyNameStrategy extends PropertyNamingStrategy.PropertyNamingStrategyBase {
#Override
public String translate(String propertyName) {
return propertyName.toUpperCase();
}
}
You can declare the bean in xml as well.
It won't override #JsonProperty that define the prop name explicitly.
I'm working with java and Spring MVC, In the first version of the app I was response with a ResponseEntity<String> and where I haved and error I returned something like return new ResponseEntity<String>(httpErrors.toString(), responseHeaders, HttpStatus.BAD_REQUEST); and when all were right something like return new ResponseEntity<String>(loginResponse.toString(), responseHeaders, HttpStatus.OK);. But now I believe theres is a better way to do it, without using the toString() method, returning the specific object according to the case like this:
#RestController
#RequestMapping("/user")
public class LoginController {
/** The login service to validate the user. */
#Autowired
LoginService loginService;
#RequestMapping(value = "/login", method = RequestMethod.POST)
public #ResponseBody ResponseEntity<?> validate(#RequestBody final UserLog login) {
WebUser webUser = loginService.getUserDetails(login.getLogin(), login.getPassword());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
if (webUser == null) {
HttpErrors httpErrors = new HttpErrors(ApiCommonResources.ERROR_402, "error" + "." + ApiCommonResources.ERROR_402, ApiCommonResources.ERROR_402_TEXT);
return new ResponseEntity<HttpErrors>(httpErrors, responseHeaders, HttpStatus.BAD_REQUEST);
}
List<Account> userAccounts = loginService.getMerchantAccounts(webUser.getMerchantId());
// Json Web Token builder
token = "b7d22951486d713f92221bb987347777";
LoginResponse loginResponse = new LoginResponse(ApiCommonResources.SUCCESS_REQUEST_CODE, token);
return new ResponseEntity<LoginResponse>(loginResponse, responseHeaders, HttpStatus.OK);
}
}
The question is how can I create a class that can wraps the LoginResponse as well as HttpErrorsobject types and send it in ? as the returning object in ResponseEntity:
LoginResponse class:
public class LoginResponse{
public LoginResponse(Integer statusCode, String token){
this.setStatusCode(statusCode);
this.setToken(token);
}
private String token;
private Integer statusCode;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Integer getStatusCode() {
return statusCode;
}
public void setStatusCode(Integer statusCode) {
this.statusCode = statusCode;
}
#Override
public String toString() {
StringBuilder jsonResponse = new StringBuilder();
jsonResponse.append("{");
jsonResponse.append("\"statusCode\":");
jsonResponse.append("\"" + statusCode + "\",");
jsonResponse.append("\"token\":");
jsonResponse.append("\"" + token + "\"");
jsonResponse.append("}");
return jsonResponse.toString();
}
}
And HttpErrors class:
public class HttpErrors {
public HttpErrors(){
}
public HttpErrors(String errorCode, String errorKey, String errorMessage) {
super();
this.errorCode = errorCode;
this.errorKey = errorKey;
this.errorMessage = errorMessage;
}
private String errorCode;
private String errorKey;
private String errorMessage;
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorKey() {
return errorKey;
}
public void setErrorKey(String errorKey) {
this.errorKey = errorKey;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
#Override
public String toString() {
StringBuilder jsonError = new StringBuilder();
jsonError.append("{");
jsonError.append("\"errorCode\":");
jsonError.append("\"" + errorCode + "\",");
jsonError.append("\"errorKey\":");
jsonError.append("\"" + errorKey + "\",");
jsonError.append("\"errorMessage\":");
jsonError.append("\"" + errorMessage + "\"");
jsonError.append("}");
return jsonError.toString();
}
}
public class Response<T> {
private int httpStatus;
private T data;
//getter and setter consructor
eg constructors
public RestResponse(T data){
this(HTTP_OK,data)
}
public RestResponse(int httpStatus,T data){
this.httpStatus = httpStaus;
this.data = data;
}
Now just use this template for all your response objects (repsone objects can be POJOs too)
return new RestEntity<LoginResponse>(loginResponse,statusCode) //loginResponse object
where LoginResponse is
public class LoginResponse {
private String token;
//getter and setter and constructors.
}
You should take some time to establish a REST contracts (Read about it using google :)) and then just follow through using this basic logic. Java and spring are magic together.
Have fun.
maybe try something like this (in my opinion it will be more elegant)
create a method in controller which returns LoginResponse, but firstly perform validation of the input UserLog and once there are any issues, throw a custom exception, which in the end will be caught by the exceptionHandler
take a look at my example controller
#RestController
public class ProductController {
private ProductRequestValidator productRequestValidator;
#InitBinder
public void initBinder(WebDataBinder binder){
binder.addValidators(productRequestValidator);
}
#Autowired
public ProductController(ProductRequestValidator productRequestValidator, ProductService productService) {
this.productRequestValidator = productRequestValidator;
}
#RequestMapping(value = "/products", method = RequestMethod.GET)
public List<ProductResponse> retrieveProducts(#Valid #RequestBody ProductRequest requestProduct, BindingResult bindingResult)
throws ValidationException {
// validate input and throw exception if any error occured
if (bindingResult.hasErrors()){
throw new ValidationException(bindingResult);
}
// business logic
return new ProductRequest();
}
if you want you can check my bitbucket project which has it all implemented:
controller
exceptionHandler
customException
customValidator
I can't make spring return a serialization of my object with the additional property defining the class.
My classes are:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include= JsonTypeInfo.As.PROPERTY, property="ObjectType")
#JsonSubTypes({
#JsonSubTypes.Type(value=LiteStudy.class, name="LiteStudy")
})
public class Entity {
...
}
#JsonTypeName("LiteStudy")
#JsonSubTypes({
#JsonSubTypes.Type(value=Study.class, name="Study")
})
public class LiteStudy extends Entity {
...
}
#JsonTypeName("Study")
public class Study extends LiteStudy{
...
}
In my unit test, an Study instance is serialised properly, having the extra property for the class:
{"ObjectType":"Study",
...
}
Using for this a simple:
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(studyJSON,study.getClass());
However, in my Spring Rest webservice module the study is serialized without the "ObjectType" property.
The controller looks like this (simplified):
#ResponseBody
public RestResponse<Study> getStudyById(#PathVariable("studyIdentifier") String studyIdentifier) throws DAOException {
return getStudyRestResponse(studyIdentifier);
}
EDIT: Adding RestResponse (Simplified)
public class RestResponse<Content> {
private Content content;
private String message;
private Exception err;
public Content getContent() {
return content;
}
public void setContent(Content content) {
this.content = content;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Exception getErr() {
return err;
}
public void setErr(Exception err) {
this.err = err;
}
Any idea why spring seems to be ignoring the #JsonType annotations?
Try to return only the object you need, do not wrap it in generic wrapper class. Your problem is related to Java Type erasure. See more info here
#ResponseBody
public #ResponseBody Study getStudyById(#PathVariable("studyIdentifier") String studyIdentifier) throws DAOException {
return studyIdentifier;
}
I have read following question:
https://stackoverflow.com/a/25609465/2674303
I have spring-mvc application. If I annotate controller method with #ResponseBody annotation and return object inside method, then server gives json to clients. I have Jackson in classpath.
In mentioned example wrote following code
ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(new SimpleFilterProvider().addFilter("filter", new ExcludeIdFilter()));
As I understand this code already written somewhere in spring internals...
Please clarify how to register custom jackson filter ?
You can configure your own instance of MappingJackson2HttpMessageConverter like this:
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper mapper = Jackson2ObjectMapperBuilder().json()
.filters(new SimpleFilterProvider().addFilter("filter", new ExcludeIdFilter()));
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(mapper);
// then replace the default MappingJackson2HttpMessageConverter
// with your custom one in the list of configured converters
}
}
You can handle it via defined your mapping in <mvc:message-converters> Below example just used StringHttpMessageConverter so it means all String fields are converted as json at your responseValueObjects, if you want your objects also convert to jSon you should write your custom message convertor and set it at <mvc:message-converters>.
spring-servlet.xml
<!-- Activate to Spring MVC annotion like #RequestMapping or #Controller -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter" />
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
</mvc:message-converters>
</mvc:annotation-driven>
Assume you query your users, below is the Controller class.
#Controller
#RequestMapping(value = "api/queryUser")
public class ApiQueryUser {
#Autowired
private UserService userService;
#Autowired
private ValidationService validationService;
#RequestMapping(method = RequestMethod.POST)
#ResponseBody
public UserResponseValue queryUser(HttpServletRequest request) {
UserResponseValue userResponseValue = new UserResponseValue();
String userName = request.getParameter("USERNAME");
String password = request.getParameter("PASSWORD");
String email = request.getParameter("EMAIL");
try {
// validationService.validateParamaterNotNull(userName, password);
BR_User user = userService.queryUser(userName, password, email);
userResponseValue.setUserName(user.getUserName());
userResponseValue.setEmail(user.getEmail());
userResponseValue.setRole(user.getRole());
userResponseValue.setResponseCode("100");
userResponseValue.setResponseMessage("User exist");
} catch (ValidationException e) {
userResponseValue.setResponseCode("99");
userResponseValue.setErrorCode(e.getErrorCode().name());
} catch (ApiException e) {
userResponseValue.setResponseCode("98");
userResponseValue.setErrorCode(e.getErrorCode().name());
} catch (Exception e) {
userResponseValue.setResponseCode("96");
userResponseValue.setErrorCode(ErrorCode.ERR20000.name());
}
return userResponseValue;
}
}
finally responseValue object, take care that all the fileds are String
public class UserResponseValue{
private String userName;
private String role;
private String email;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
and be sure Jackson dependency is added at your pom.xml
<!-- To use responseBody as a default JSON messageConverter -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.1</version>
</dependency>
This is working example from my project. I am using Spring 4.
I have found temporary this solution :
#ControllerAdvice
public class JsonFilterAdvice implements ResponseBodyAdvice<List<?>>
{
#Override
public List<?> beforeBodyWrite(
List<?> arg0,
MethodParameter arg1,
MediaType arg2,
Class<? extends HttpMessageConverter<?>> arg3,
ServerHttpRequest arg4,
ServerHttpResponse arg5)
{
HttpServletRequest servletRequest = ((ServletServerHttpRequest) arg4).getServletRequest();
String[] params = servletRequest.getParameterValues("filters");
if (params != null)
{
// parse object and set field to null
}
return arg0;
}
#Override
public boolean supports(MethodParameter arg0, Class<? extends HttpMessageConverter<?>> arg1)
{
// return true if method parameters contain 'filters' field
return true;
}
any other suggestions are welcome