I have a multi-module Maven-based Spring Boot Application. In one of my modules' application.properties file, I am setting Jackson to not serialize dates as timestamps (arrays), so that they are always serialized as strings (which is the fallback behavior). The property is this:
spring.jackson.serialization.write_dates_as_timestamps=false
Problem is... the property doesn't seem to be picked up by either Spring Boot/Jackson. The java.time.LocalDate instances I am trying to serialize always get written as timestamps. I have checked the code inside the LocalDateSerializer.serialize(...) method (from Jackson's own JavaTimeModule), and found this:
#Override
public void serialize(LocalDate date, JsonGenerator generator, SerializerProvider provider) throws IOException
{
if (useTimestamp(provider)) { // This always returns true
generator.writeStartArray();
generator.writeNumber(date.getYear());
generator.writeNumber(date.getMonthValue());
generator.writeNumber(date.getDayOfMonth());
generator.writeEndArray();
} else {
String str = (_formatter == null) ? date.toString() : date.format(_formatter);
generator.writeString(str);
}
}
The useTimestamp() method always returns true, no matter what my configuration is. :(
Maybe you can create a custom MappingJackson2HttpMessageConverter and an ObjectMapper. Here is an example configuration class,
#Configuration
public class MyConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MappingJackson2HttpMessageConverter
getMappingJacksonHttpMessageConverter() {
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter();
...
ObjectMapper mapper = new ObjectMapper();
...
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
//or
// mapper.configure(
// SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
converter.setObjectMapper(mapper);
return converter;
}
}
Thanks to Indra's answer, I got an important clue that helped me figure out the issue.
Turns out that my Spring Boot application has multiple instances of RestTemplate in its application context (it is a pretty big system and relies upon some custom proprietary frameworks to work). Most of these RestTemplate instances were there just to support my system's architecture. There is only one RestTemplate instance that I should've cared about: The one that I wrote and allows my business logic to work (by enabling my system to consume remote RESTful APIs).
So, when I was trying to configure Jackson and its ObjectMapper, I wasn't really affecting my RestTemplate instance. By the time my configuration was read, that RestTemplate had long ago been initialized with the default settings provided by Spring Boot.
This is how my final configuration class looks:
#Configuration
public class RestConfiguration {
#Bean
public RestTemplate myRestTemplate(HttpMessageConverters customConverters) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(customConverters.getConverters());
return restTemplate;
}
/*
* The following custom HttpMessageConverter is injected into myRestTemplate
* in order to appropriately configure the serialization/deserialization of LocalDate
* instances into/from strings.
*/
#Bean
public HttpMessageConverters customConverters() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
converter.setObjectMapper(mapper);
return new HttpMessageConverters(converter);
}
}
What I am doing is to explicitly configure the RestTemplate instance that needed to be configured.
The following documentation was also very helpful: HttpMessageConverters. It describes how to declare custom HttpMessageConverters for use in the application context.
Related
I have created this class to add GSON to the spring-boot classpath. The problem, I cannot view any Html/text, or images content since there is not a valid converter.
I am relatively new to Spring-Boot.
I would like to to add Gson instead of Jackson to spring-boot and also be able to brow the web app using chrome.
Here is my class:
#Configuration
#EnableWebMvc
public class GsonSpringBootConfigurer implements WebMvcConfigurer {
private static final Logger log = LoggerFactory.getLogger(SyncController.class);
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
for(HttpMessageConverter httpMessageConverter : converters){
log.info(httpMessageConverter.toString());
}
converters.add(createGsonHttpMessageConverter());
}
private GsonHttpMessageConverter createGsonHttpMessageConverter() {
Gson gson = JsonUtils.getGsonBuilder();
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson);
return converter;
}
}
Note the Javadoc of configureMessageConverters states
Configure the HttpMessageConverters to use for reading or writing to
the body of the request or response. If no converters are added, a
default list of converters is registered.
Note that adding converters to the list, turns off default converter
registration. To simply add a converter without impacting default
registration, consider using the method
extendMessageConverters(java.util.List) instead.
In other words, you've removed all the converters that handle other content types.
Note that Spring MVC only registers the Jackson HttpMessageConverter (MappingJackson2HttpMessageConverter) if the corresponding Jackson libraries are in your classpath. You could remove them and, assuming you have Gson in your classpath, a GsonHttpMessageConverter will be registered for you.
From your code, it seems you want to create a custom GsonHttpMessageConverter. In that case, you can follow the Javadoc instructions and use extendMessageConverters.
A hook for extending or modifying the list of converters after it has
been configured. This may be useful for example to allow default
converters to be registered and then insert a custom converter through
this method.
You'd first want to remove the existing instance, then add your own. For example,
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
// remove the Jackson version if Jackson is still in your classpath
converters.removeIf(converter -> converter instanceof MappingJackson2HttpMessageConverter);
// remove the existing instance (from defaults)
converters.removeIf(converter -> converter instanceof GsonHttpMessageConverter);
// add your custom
converters.add(createGsonHttpMessageConverter());
}
Is there a way to create two instances of ObjectMapper for different purpose.
Modified ObjectMapper
#Component
class MyObjectMapper extends ObjectMapper{
public MyObjectMapper(){
super();
}
public MyObjectMapper(MyObjectMapper om) {
super(om);
}
#Override
public MyObjectMapper copy() {
return new MyObjectMapper(this);
}
}
Now use it as follows
#Autowired ObjectMapper objectMapper; //performs standard serialization
#Autowire MyObjectMapper myMapper; //i can add custom filters in thiis mapper.
I tried a similar setup but the custom mapper actually affects the original mapper all the rest controllers throw JSON parse error: Unrecognized field
Edit: Not sure if this point is very important but still adding it
Am using spring-boot-starter-json
And that's exactly where you should use #Qualifier annotation.
This annotation may be used on a field or parameter as a qualifier for candidate beans when autowiring. It may also be used to annotate other custom annotations that can then in turn be used as qualifiers.
OK. Combining with Answer from Aniket figured out what is wrong and still looking for some more explanation.
Instead of instantiating the ObjectMapper as new ObjectMapper(). Building it with Mapper fixed it.
So, two have multiple instance of ObjectMapper
#Primary
#Bean
public ObjectMapper objectMapper(){
return new Jackson2ObjectMapperBuilder()
.build();
}
#Bean("customMapper")
public ObjectMapper customMapper(){
ObjectMapper customMapper = new Jackson2ObjectMapperBuilder().build();
mapper.<your customization , filters, providers etc;>
return mapper;
}
The #Primary will be used by in all default cases i.e When you simply #Autowire or the default serialization applied to your Response/Request Body by your controller.
To use your Custom Mapper, explicitly use with the Bean ID.
#Autowired #Qualifier("customMapper") ObjectMapper mapper;
I'm trying to implement RFC 7807 in my Spring Boot project using zalando problem-spring-web https://github.com/zalando/problem-spring-web
I've done the setup according to this guide https://github.com/zalando/problem-spring-web/tree/master/problem-spring-web
When an exception is thrown, the Problem instance does get generated, but its serialized JSON form is not as expected, most notably the stack trace is included when it shouldn't.
After some debugging, it seems that the ProblemModule is not registered in the ObjectMapper that is used to serialize the problem (its setupModule method is never called). I was under the impression that declaring a bean of type Module was enough to have it picked up by Spring and registered in the ObjectMapper, but it doesn't happen here.
The doc says
In case you want to enable stack traces, please configure your
ProblemModule as follows:
ObjectMapper mapper = new ObjectMapper()
.registerModule(new ProblemModule().withStackTraces());
which seems to imply that you need to instantiate your own ObjectMapper, but then how to make sure that it's used by the library when deserializing the Problem?
Since I can't get the ObjectMapper to register my Modules I figured I had to do it myself so I came up with this solution that seems to work:
#Configuration
public class ProblemConfiguration implements InitializingBean {
#Autowired
ObjectMapper objectMapper;
#Override
public void afterPropertiesSet() {
objectMapper.registerModules(
new ProblemModule(),
new ConstraintViolationProblemModule()
);
}
}
If someone has an idea why it's not working as expected, I'd be glad to hear it :)
Since Spring boot 1.1.0, the JacksonAutoConfiguration creates an ObjectMapper bean and automatically registers all module found in your registered beans,
so you juste need to create does two beans modules and use de already configured ObjectMapper like this:
/*
* Module for serialization/deserialization of RFC7807 Problem.
*/
#Bean
public ProblemModule problemModule() {
return new ProblemModule();
}
/*
* Module for serialization/deserialization of ConstraintViolationProblem.
*/
#Bean
public ConstraintViolationProblemModule constraintViolationProblemModule() {
return new ConstraintViolationProblemModule();
}
use configured ObjectMapper for ex in service Classe
#Autowired
ObjectMapper jacksonObjectMapper
I am using SpringBoot 1.5.9., Jackson 2.8 and Spring Framework 4.3.13.
I am trying to register and use the AfterburnerModel.
According to the Spring Boot documentation, to configure the ObjectMapper you can either define the bean yourself and annotate it with #Bean and #Primary. In the bean you can register a module. Or you can add a bean of type Jackson2ObjectMapperBuilder where you can customize the ObjectMapper, by adding a module.
I have tried both ways, and during serialization none of my breakpoints in jackson-module-afterburner fire. My customizations are being read, but seem to be being ignored.
By default Spring MVC MappingJackson2HttpMessageConverter will create its own ObjectMapper with default options using Jackson2ObjectMapperBuilder. As per Spring Boot docs 76.3 Customize the Jackson ObjectMapper chapter:
Any beans of type com.fasterxml.jackson.databind.Module are automatically registered with the auto-configured Jackson2ObjectMapperBuilder and are applied to any ObjectMapper instances that it creates. This provides a global mechanism for contributing custom modules when you add new features to your application.
so it should be enough to register your module as a bean:
#Bean
public AfterburnerModule afterburnerModule() {
return new AfterburnerModule();
}
A more detailed setup can be achieved with #Configuration class to customize the MappingJackson2HttpMessageConverter:
#Configuration
public class MyMvcConf extends WebMvcConfigurationSupport {
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(myConverter());
addDefaultHttpMessageConverters(converters);
}
#Bean
public MappingJackson2HttpMessageConverter myConverter() {
return new MappingJackson2HttpMessageConverter(myObjectMapper())
}
#Bean
public ObjectMapper myObjectMapper() {
return new ObjectMapper().registerModule(new AfterburnerModule());
}
}
I'm using spring-boot and want to customize the ObjectMapper created.
What I want to do is be able to serialize objects that do not have a getter or setters. Before this could be done by putting JsonAutoDetect.Visibility.ANY on the ObjectMapper.
But how can I enable this feature using the Jackson2ObjectMapperBuilder bean I'm currently exposing ?
You can use a Jackson2ObjectMapperBuilder subclass that overrides the configure(ObjectMapper) method:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return new Jackson2ObjectMapperBuilder() {
#Override
public void configure(ObjectMapper objectMapper) {
super.configure(objectMapper);
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
}
};
}
If you want to keep the ObjectMapper configurable through the spring.jackson.* properties that Spring Boot provides, then you better don't define your own Jackson2ObjectMapperBuilder bean (check JacksonObjectMapperBuilderConfiguration inside JacksonAutoConfiguration class for details).
What you can do instead is this:
#Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder mapperBuilder) {
return mapperBuilder.build().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
}
I spend a half of day to play with different settings.
So I manage to work it (1.3.2.RELEASE) when:
I configure jackson in simple #Configuration annotated config class (not extended from WebMvcConfigurerAdapter)
I have NOT#EnableWebMvc
Then Jackson2ObjectMapperBuilder objectMapperBuilder solution is
work, but spring.jackson.serialization.indent_output: true in properties ignored.
At last I finished with
#Autowired(required = true)
public void configeJackson(ObjectMapper objectMapper) {
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
But all this is puzzle for me. I wrote a question about any explanation of all this magic in order to have some undestanding and solve problem not by
trial-and-error method: Are there any Spring Boot documentation for understanding how web mvc configuration is work?