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?
Related
I've read many articles/threads and what not about how to enable Jackson's WRAP_ROOT_VALUE feature in SpringBoot (v2.2.2RELEASE) and none of them really work, until I came across the following solution which does the trick!
#Configuration
public class JacksonConfig
#Bean
#Primary
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder() //
.featuresToEnable(SerializationFeature.WRAP_ROOT_VALUE); // enables wrapping
return builder;
}
This works since it replaces SpringBoot/Swagger's serialization definitions with this custom one.
The problem: Swagger3 stop working! I access swagger using this link:
http://localhost:8080/swagger-ui/index.html and it stopped working! If I remove the JacksonConfig then everything is back to normal.
My assumption is that Swagger initializes Jackson in some way that ruins my custom one.
Any idea?
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 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 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.
I have Spring Boot project with Maven dependency: com.fasterxml.jackson.datatype
And I want to enable two properties ACCEPT_EMPTY_STRING_AS_NULL_OBJECT and FAIL_ON_READING_DUP_TREE_KEY.
But fail two enable them in two different ways:
1) application.yml
jackson:
serialization:
WRITE_DATES_AS_TIMESTAMPS: false
deserialization:
FAIL_ON_READING_DUP_TREE_KEY: true
2) Adding them as Configuration Bean
#Configuration
public class JacksonConfiguration {
#Autowired
private ObjectMapper objectMapper;
#PostConstruct
private void configureObjectMapper() {
objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT );
objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY );
}
}
Neither one of this ways gave me desired effect. Could you please advice correct way how to do it?
I tried to use two options for FasterXml Jackson:
ACCEPT_EMPTY_STRING_AS_NULL_OBJECT to automatically map empty
strings to null values. And look like it is not working jet
properly, according to
https://github.com/FasterXML/jackson-databind/issues/1563 So I
solved problem with writing custom deserialiser.
FAIL_ON_READING_DUP_TREE_KEY to enable Strict JSON validation, but the desired effect I get with JsonParser.Feature.STRICT_DUPLICATE_DETECTION.
So now I end up with two working solutions:
#Bean
public ObjectMapper objectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
return objectMapper;
}
and application.yml
jackson:
serialization:
WRITE_DATES_AS_TIMESTAMPS: false
parser:
STRICT_DUPLICATE_DETECTION: true
I will use application.yml, of course, to keep configuration compact and in one place.
Thanks to #Michal Foksa I will accept your answer, because it is one of the ways how to configure ObjectMapper properly.
Create and configure ObjectMapper from scratch:
#Configuration
public class JacksonConfiguration {
#Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT )
.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY );
}
}