How to make injected objectmapper output locale-specific - java

I have a Spring Boot REST controller with the method
#GetMapping(value = "/validate", produces = MimeTypeUtils.APPLICATION_JSON_VALUE)
#ResponseBody
public Result validate(
#ApiParam(value = "http://example.org/test", required = true) #RequestParam String iri,
#Context HttpServletRequest request
) {
return service.validate(iri);
}
...
And a custom object mapper configured as (Result class is part of a third-party library)
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
SimpleModule module = new SimpleModule();
module.addSerializer(Result.class, new ResultSerializer());
mapper.registerModule(module);
return mapper;
}
Inside ResultSerializer (my custom implementation) I need to customize the serialization output based on the Accept-language header (e.g. from the bounding HttpServletRequest). I am currently only able to solve by not using Spring injection for the ObjectMapper, explicitly creating an ObjectMapper instance in my controller, executing it and returning the output. So:
Can an Accept-language header be obtained in the ObjectMapper instance injected as a Spring bean?

A possibility (not knowing exactly what you are trying to customize): You could try making your ResultSerializer request-scoped.
#Bean
#RequestScope
public ResultSerializer resultSerializer() {
return new ResultSerializer();
}
Then you can simply inject your HttpServletRequest into the ResultSerializer.
public class ResultSerializer {
#Autowired
private HttpServletRequest request;
}

Thanks to Marco, I managed to adjust slightly his solution to get it working using:
#Bean
#RequestScope
public ObjectMapper objectMapper(#Autowired HttpServletRequest request) {
...
module.addSerializer(Result.class, new ResultSerializer(request.getLocale().toLanguageTag()));
...
}

Related

Hot to make jackson to use snake_case / camelCase on demand in a Spring Boot REST API?

I want to support both snake_case and camelCase in the json interface of my rest API.
Jackson uses camelCase by default. I know there are many ways of making Jackson to use snake_case:
Annotating properties individually: #JsonProperty("first_name")
Annotating classes with: #JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
Applying it to the whole application inside application.properties:
spring.jackson.property-naming-strategy=SNAKE_CASE
But I haven't found information about how to allow the consumer to request the desired case. For example by using content negotiation (Accepts, Content-Type headers)
Lets suppose we have the following example:
#RequestMapping("/my-resource")
public interface ApiController {
#RequestMapping(method = RequestMethod.POST, produces = {"application/json"})
MyResource createResource(#RequestBody MyResource myResource);
#GetMapping(value="/{resourceId}", produces = {"application/json"})
MyResource readResource(#PathVariable String resourceId);
}
So one use case could be to use custom json media-type and provide content negotiation headers like:
Accepts: application/vnd.snake+json
and let the default application/json to use camelCase.
But I cannot find the way of making jackson to use two different Mapper configurations based on the content negotiation.
Good question, I would suggest to take a look into content negotiation and http message converter topics.
It seems you can configure two instances of MappingJackson2HttpMessageConverter
to produce application/vnd.snake+json or application/json.
Set it with specific media type and differently configured ObjectMapper
Code modification can looks like:
MyMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter
{
public MyMappingJackson2HttpMessageConverter(ObjectMapper objectMapper, MediaType mediaType)
{
super(objectMapper, mediaType);
}
}
Configuration can looks like:
#EnableWebMvc
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
converters.add(new MyMappingJackson2HttpMessageConverter(snake, new MediaType("application", "vnd.snake+json")));
converters.add(new MappingJackson2HttpMessageConverter(camel, MediaType.APPLICATION_JSON));
super.configureMessageConverters(converters);
}
...
As per new version of spring boot, I end up doing this way -
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverterSnakeCase = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverterSnakeCase.setSupportedMediaTypes(Arrays.asList(new MediaType("application", "vnd.snake+json")));
mappingJackson2HttpMessageConverterSnakeCase.setObjectMapper(objectMapperSnakeCase());
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverterCamelCase = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverterCamelCase.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON));
mappingJackson2HttpMessageConverterCamelCase.setObjectMapper(objectMapperSnakeCase());
converters.add(mappingJackson2HttpMessageConverterCamelCase);
converters.add(mappingJackson2HttpMessageConverterSnakeCase);
WebMvcConfigurer.super.configureMessageConverters(converters);
}

Can you configure Spring controller specific Jackson deserialization?

I need to add a custom Jackson deserializer for java.lang.String to my Spring 4.1.x MVC application. However all answers (such as this) refer to configuring the ObjectMapper for the complete web application and the changes will apply to all Strings across all #RequestBody in all controllers.
I only want to apply the custom deserialization to #RequestBody arguments used within particular controllers. Note that I don't have the option of using #JsonDeserialize annotations for the specific String fields.
Can you configure custom deserialization for specific controllers only?
To have different deserialization configurations you must have different ObjectMapper instances but out of the box Spring uses MappingJackson2HttpMessageConverter which is designed to use only one instance.
I see at least two options here:
Move away from MessageConverter to an ArgumentResolver
Create a #CustomRequestBody annotation, and an argument resolver:
public class CustomRequestBodyArgumentResolver implements HandlerMethodArgumentResolver {
private final ObjectMapperResolver objectMapperResolver;
public CustomRequestBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
this.objectMapperResolver = objectMapperResolver;
}
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(CustomRequestBody.class) != null;
}
#Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
if (this.supportsParameter(methodParameter)) {
ObjectMapper objectMapper = objectMapperResolver.getObjectMapper();
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
return objectMapper.readValue(request.getInputStream(), methodParameter.getParameterType());
} else {
return WebArgumentResolver.UNRESOLVED;
}
}
}
#CustomRequestBody annotation:
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CustomRequestBody {
boolean required() default true;
}
ObjectMapperResolver is an interface we will be using to resolve actual ObjectMapper instance to use, I will discuss it below. Of course if you have only one use case where you need custom mapping you can simply initialize your mapper here.
You can add custom argument resolver with this configuration:
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public CustomRequestBodyArgumentResolver customBodyArgumentResolver(ObjectMapperResolver objectMapperResolver) {
return new CustomRequestBodyArgumentResolver(objectMapperResolver)
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(customBodyArgumentResolver(objectMapperResolver()));
}
}
Note: Do not combine #CustomRequestBody with #RequestBody, it will be ignored.
Wrap ObjectMapper in a proxy that hides multiple instances
MappingJackson2HttpMessageConverter is designed to work with only one instance of ObjectMapper. We can make that instance a proxy delegate. This will make working with multiple mappers transparent.
First of all we need an interceptor that will translate all method invocations to an underlying object.
public abstract class ObjectMapperInterceptor implements MethodInterceptor {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return ReflectionUtils.invokeMethod(invocation.getMethod(), getObject(), invocation.getArguments());
}
protected abstract ObjectMapper getObject();
}
Now our ObjectMapper proxy bean will look like this:
#Bean
public ObjectMapper objectMapper(ObjectMapperResolver objectMapperResolver) {
ProxyFactory factory = new ProxyFactory();
factory.setTargetClass(ObjectMapper.class);
factory.addAdvice(new ObjectMapperInterceptor() {
#Override
protected ObjectMapper getObject() {
return objectMapperResolver.getObjectMapper();
}
});
return (ObjectMapper) factory.getProxy();
}
Note: I had class loading issues with this proxy on Wildfly, due to its modular class loading, so I had to extend ObjectMapper (without changing anything) just so I can use class from my module.
It all tied up together using this configuration:
#Configuration
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(objectMapper(objectMapperResolver()));
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jackson2HttpMessageConverter());
}
}
ObjectMapperResolver implementations
Final piece is the logic that determines which mapper should be used, it will be contained in ObjectMapperResolver interface. It contains only one look up method:
public interface ObjectMapperResolver {
ObjectMapper getObjectMapper();
}
If you do not have a lot of use cases with custom mappers you can simply make a map of preconfigured instances with ReqeustMatchers as keys. Something like this:
public class RequestMatcherObjectMapperResolver implements ObjectMapperResolver {
private final ObjectMapper defaultMapper;
private final Map<RequestMatcher, ObjectMapper> mapping = new HashMap<>();
public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper, Map<RequestMatcher, ObjectMapper> mapping) {
this.defaultMapper = defaultMapper;
this.mapping.putAll(mapping);
}
public RequestMatcherObjectMapperResolver(ObjectMapper defaultMapper) {
this.defaultMapper = defaultMapper;
}
#Override
public ObjectMapper getObjectMapper() {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = sra.getRequest();
for (Map.Entry<RequestMatcher, ObjectMapper> entry : mapping.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return defaultMapper;
}
}
You can also use a request scoped ObjectMapper and then configure it on a per-request basis. Use this configuration:
#Bean
public ObjectMapperResolver objectMapperResolver() {
return new ObjectMapperResolver() {
#Override
public ObjectMapper getObjectMapper() {
return requestScopedObjectMapper();
}
};
}
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public ObjectMapper requestScopedObjectMapper() {
return new ObjectMapper();
}
This is best suited for custom response serialization, since you can configure it right in the controller method. For custom deserialization you must also use Filter/HandlerInterceptor/ControllerAdvice to configure active mapper for current request before the controller method is triggered.
You can create interface, similar to ObjectMapperResolver:
public interface ObjectMapperConfigurer {
void configureObjectMapper(ObjectMapper objectMapper);
}
Then make a map of this instances with RequstMatchers as keys and put it in a Filter/HandlerInterceptor/ControllerAdvice similar to RequestMatcherObjectMapperResolver.
P.S. If you want to explore dynamic ObjectMapper configuration a bit further I can suggest my old answer here. It describes how you can make dynamic #JsonFilters at run time. It also contains my older approach with extended MappingJackson2HttpMessageConverter that I suggested in comments.
Probably this would help, but it ain't pretty. It would require AOP. Also I did not validate it.
Create a #CustomAnnotation.
Update your controller:
void someEndpoint(#RequestBody #CustomAnnotation SomeEntity someEntity);
Then implemment the AOP part:
#Around("execution(* *(#CustomAnnotation (*)))")
public void advice(ProceedingJoinPoint proceedingJoinPoint) {
// Here you would add custom ObjectMapper, I don't know another way around it
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String body = request .getReader().lines().collect(Collectors.joining(System.lineSeparator()));
SomeEntity someEntity = /* deserialize */;
// This could be cleaner, cause the method can accept multiple parameters
proceedingJoinPoint.proceed(new Object[] {someEntity});
}
You can create custom deserializer for your String data.
Custom Deserializer
public class CustomStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String str = p.getText();
//return processed String
}
}
Now suppose the String is present inside a POJO use #JsonDeserialize annotation above the variable:
public class SamplePOJO{
#JsonDeserialize(using=CustomStringDeserializer.class)
private String str;
//getter and setter
}
Now when you return it as a response it will be Deserialized in the way you have done it in CustomDeserializer.
Hope it helps.
You could try Message Converters.
They have a context about http input request (for example, docs see here, JSON). How to customize you could see here.
Idea that you could check HttpInputMessage with special URIs, which used in your controllers and convert string as you want.
You could create special annotation for this, scan packages and do it automatically.
Note
Likely, you don't need implementation of ObjectMappers. You can use simple default ObjectMapper to parse String and then convert string as you wish.
In that case you would create RequestBody once.
You can define a POJO for each different type of request parameter that you would like to deserialize. Then, the following code will pull in the values from the JSON into the object that you define, assuming that the names of the fields in your POJO match with the names of the field in the JSON request.
ObjectMapper mapper = new ObjectMapper();
YourPojo requestParams = null;
try {
requestParams = mapper.readValue(JsonBody, YourPOJO.class);
} catch (IOException e) {
throw new IOException(e);
}

Spring MVC Rest Controller #RequestBody Parsing

I have a spring rest web app that contains a a generic rest controller like below. The GET methods are working fine having Jackson serializing the objects to JSON. However when I try to call the save method, the RequestBody parameter is being converted to LinkedHashMap instead of the type defined by the T generic type.
#RestController
public abstract class CrudAPI<T extends Object, ID extends Serializable>{
#Transactional
#RequestMapping(method = RequestMethod.POST, consumes = "application/json")
public ResponseEntity<Void> save(#RequestBody T entity){
service.save(entity);
return new ResponseEntity(HttpStatus.CREATED);
}
}
The JSON:
{
"id":null,
"version":null,
"name":"Copel",
"disabled":false,
"type":"P",
"labels":[
{
"id":null,
"version":null,
"name":"unidade consumidora"
}
]
}
I get the following error:
HTTP Status 500 - Request processing failed; nested exception is
org.springframework.beans.NotReadablePropertyException: Invalid
property 'version' of bean class [java.util.LinkedHashMap]: Could not
find field for property during fallback access!
The Spring configuration:
#Configuration
#Import(Application.class)
#EnableWebMvc
#ComponentScan(basePackages = {"br.com.doc2cloud"})
public class WebConfig extends WebMvcConfigurerAdapter implements WebApplicationInitializer {
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
private MappingJackson2HttpMessageConverter jacksonConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Hibernate5Module());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.setDateFormat(new ISO8601DateFormat());
mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
jacksonConverter.setObjectMapper(mapper);
return jacksonConverter;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(jacksonConverter());
super.configureMessageConverters(converters);
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter("javax.servlet.jsp.jstl.fmt.localizationContext", "messages");
EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD);
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
FilterRegistration.Dynamic characterEncoding = servletContext.addFilter("characterEncoding", characterEncodingFilter);
characterEncoding.addMappingForUrlPatterns(dispatcherTypes, true, "/*");
}
}
What is wrong with my code?
I don't think you can achieve what you want. Java generics mechanism works only in compile-time. After compilation generic types are erased and are replaced by actual types (Object in your case). There is no way your controller will understand to what type you try to parse JSON data.
The reason why you get LinkedHashMap is that Jackson uses it by default for "unknown" types. Typical JSON data is actually a key-value map and Jackson preserves attributes ordering - that's why linked hash map is used.
Which version of Jackson are you using? I upgraded to 2.7.3 and when using generics (I have a base controller with common logic for saving, listing, etc) I had the same issue. Rolling back to 2.6.5 allowed me to continue using my generic base class. I haven't yet researched the reason for the issue but rolling back fixed it for me.

Unable to deserialize ZonedDateTime using Jackson

I am using Jersey with Jackson as JSON provider. I am able to serialize ZonedDateTime to JSON but when I want to deserialize it gives me error as follows.
Could you please help me tell the exact configuration required to get this deserialization work.
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class java.time.ZonedDateTime] from String value ('2016-01-21T21:00:00Z'); no single-String constructor/factory method
My mapper configuration is as follows:
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper MAPPER;
public ObjectMapperContextResolver() {
MAPPER = new ObjectMapper();
//This would add JSR310 (Datetime) support while converting date to JSON using JAXRS service
MAPPER.registerModule(new JavaTimeModule());
//Below line would disable use of timestamps (numbers),
//and instead use a [ISO-8601 ]-compliant notation, which gets output as something like: "1970-01-01T00:00:00.000+0000".
MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return MAPPER;
}
}
I found the problem, actually issue was not with Deserialization using standard jackson provider. In my case, I was using Jersey client to get JSON and then deserialize using readEntity method.
Problem was that jersey client was not aware of the jsr310 module, so by registering the contextresolver where jsr310 has been added solved the issue. So in nutshell, you don't need to do anything for seralization and deserialization of ZonedDateTime if using normal jackson provider.
Below is the reference code which I am referring here, to get better clarity.
public class RESTClientImpl{
/*
* ***This is very important, JacksonJsonProvider is the implementation of
* MessageBodyWriter/Reader which is required for "readEntity" method,
* else it would throw MessageBodyWriter/Reader not found exception
*
* https://jersey.java.net/documentation/latest/message-body-workers.html#mbw.ex.client.mbr.reg
*
* Registering of ObjectMapperContextResolver is important as we have registered JSR310 module there and without registering this,
* Jersey client is not aware of JSR310 module, so it will not be able to de-serialize ZonedDateTime
*/
private final Client client = ClientBuilder.newClient(new ClientConfig().register(LoggingFilter.class)).register(JacksonJsonProvider.class)
.register(ObjectMapperContextResolver.class);
public User get(URI uri) {
WebTarget webTarget = client.target(uri);
Invocation.Builder invocationBuilder = webTarget.request(MediaType.APPLICATION_JSON);
Response response = invocationBuilder.get();
User user = response.readEntity(User.class);
return user;
}
}
#Provider
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper MAPPER;
public ObjectMapperContextResolver() {
MAPPER = new ObjectMapper();
//This would add JSR310 (Datetime) support while converting date to JSON using JAXRS service
MAPPER.registerModule(new JavaTimeModule());
//Below line would disable use of timestamps (numbers),
//and instead use a [ISO-8601 ]-compliant notation, which gets output as something like: "1970-01-01T00:00:00.000+0000".
MAPPER.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Object.class, new ZonedDateTimeDeserializer());
MAPPER.registerModule(simpleModule);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return MAPPER;
}
}

Is it possible to create a custom Jackson objectMapper for Spring without resorting to XML?

It's easy to create a custom ObjectMapper for Spring, but the configuration requires XML. I'm trying to reduce the amount of XML configuration for things that really aren't going to change without requiring a redeploy of my entire system anyway.
So the title says it all - can I use annotations or some other non-XML method to tell Spring, "Hey, please use my custom object mapper pls"?
EDIT:
This does not appear to work
#Configuration
#EnableWebMvc
public class AppConfig {
#Primary
#Bean
public ObjectMapper mapper(){
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
mapper.registerModule(new JodaModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
}
EDIT 2:
I do not believe that Spring is using my ObjectMapper. I have this code:
#Primary
#Bean
public ObjectMapper mapper(){
ObjectMapper mapper = new ObjectMapper();
JodaModule mod = new JodaModule();
mod.addSerializer(DateTime.class, new JsonSerializer<DateTime>() {
#Override
public void serialize(DateTime dateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
System.out.println("Hi, bob");
}
});
mapper.registerModule(mod);
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.enable(SerializationFeature.INDENT_OUTPUT);
return mapper;
}
but when I set a breakpoint on System.out.println("Hi, bob") it is never called - Though I'm definitely serializing a DateTime.
You can always follow the steps provided in the Spring Docs.
If you want to replace the default ObjectMapper completely, define a #Bean of that type and mark it as #Primary.
Defining a #Bean of type Jackson2ObjectMapperBuilder will allow you to customize both default ObjectMapper and XmlMapper (used in MappingJackson2HttpMessageConverter and MappingJackson2XmlHttpMessageConverter respectively).
So, either you define a #Bean with your ObjectMapper like this:
#Primary
#Bean
public ObjectMapper mapper() {
// Customize...
return new ObjectMapper().setLocale(Locale.UK);
}
Or, you define a #Bean of type Jackson2ObjectMapperBuilder and customize the builder like this:
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// Customize
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
return builder;
}
This blog entry describes the customization further.
In order to register beans using the #Bean annotation you must declare the #Bean in a #Configuration class as described in the Spring docs about Java-based container configuration.
This seems to be a bug. The Spring Boot documentation says that annotating an ObjectMapper Bean with #Primary should make the Spring context use it instead of the Spring's default mapper. However, this doesn't seem to work. I have found a workaround without using XML.
//Since Spring won't use the custom object mapper Bean defined below for
//HTTP message conversion(eg., when a Java object is returned from a controller,
//and should be converted to json using Jackson), we must override this method
//and tell it to use a custom message converter. We configure that custom converter
//below to use our customized Object mapper.
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
}
//configures the converter to use our custom ObjectMapper
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
//this line here
jsonConverter.setObjectMapper(objectMapper());
return jsonConverter;
}
//Primary annotation tells the Spring container to use this
//mapper as the primary mapper, instead of
//the Spring's defaultly configured mapper. Primary annotation
// DOESN'T work for some reason(this is most likely a bug and will be resolved in the future.
// When resolved, this Bean will be all it takes to tell Spring to use this ObjectMapper everywhere)
// That means that there won't be a need to configure the Http message converters manually(see method above).
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
configureObjectMapper(mapper);
return mapper;
}
//configure ObjectMapper any way you'd like
//This configuration tells the ObjectMapper to
//(de)serialize all fields(private,protected,public,..) of all objects
//and to NOT (de)serialize any properties(getters,setters).
private void configureObjectMapper(ObjectMapper mapper) {
//properties for jackson are fields with getters and setters
//sets all properties to NOT be serialized or deserialized
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
//tell the mapper to traverse all fields and not only default
//default=public fields + fields with getters and setters
//set all fields to be serialized and deserialized
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}

Categories

Resources