I have a Spring project, and I try to add a custom deserializer to deserialize Date properties depend on their format.
If I use it as annotation on Date property, it works fine.
But if I add the deserializer to my object mapper, it does not called when Jackson deserialize a date.
I try to apply my custom deserializer like this:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new DateDeserializer());
mapper.registerModule(module);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
return mapper;
}
I don't want to apply an annotation on Date properties every time, I want to use this deserializer by default.
What I do wrong?
Thanks the help for everyone.
Finally I found the answer at spring.io.
#Configuration
#EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.deserializerByType(Date.class, new DateDeserializer());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
Given that if you use the deserializer in an annotation on Date property then I would say that this ObjectMapper is not being used for deserialization. Try the following:
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setObjectMapper(objectMapper);
return jsonConverter;
}
I have doubt how you try to use this ObjectMapper bean inside your application.
I trust you already know that this bean is need to created inside a Configuration class. If not your bean will not register in the context. Like this for example,
#Configuration
public class MapperConfig {
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Date.class, new DateDeserializer());
mapper.registerModule(module);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
mapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);
return mapper;
}
}
The second problem might be how you try use this ObjectMapper bean. If your create new instance of ObjectMapper like this ObjectMapper objectMapper = new ObjectMapper();, that instance will not have your custom deserializer and stuff. What you can do is #Autowire the ObjectMapper instance that you have already created.
Related
I am converting my app to get rid of spring-boot, it now uses only Spring (5.3).
I've added the #EnableWebMvc configuration and I have my endpoints working properly for the most part - they return the data I want as JSON.
Previously, I customised the date format with the spring-boot property: spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
In the new pure-spring app though, it's regressed back serializing to a long value.
I've tried the following, but it doesn't seem to even use these beans at all:
#Bean
public ObjectMapper objectMapper() {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
ObjectMapper dateFormatMapper = new ObjectMapper();
dateFormatMapper.setDateFormat(dateFormat);
return dateFormatMapper;
}
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2JsonView(){
var converter = new MappingJackson2HttpMessageConverter();
converter.getObjectMapper().setDateFormat(
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") );
return converter;
}
I'm looking to customise the format globally, not on a per-field basis.
What would be the equivalent of spring.jackson.date-format for pure Spring #EnableWebMvc setup?
You can customize MappingJackson2HttpMessageConverter by using WebMvcConfigurer with #EnableWebMvc.
For example:
#Configuration
#EnableWebMvc
public class YourConfiguration implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
}
For more information, please see 1.11.7. Message Converters - Web on Servlet Stack - docs.spring.io.
I configured my own ObjectMapper for my SpringBoot application, Let say the object mapper is something like below (simplified):
#Bean
#Primary
public ObjectMapper objectMapper() {
return new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
Since the ObjectMapper is a singleton, all my REST Controller use that same ObjectMapper.
But, there is some controller that I want to ignore the null value, but I want to keep the rest of my controller to send the null value response.
Is there a way to configure it? So the best result is I can configure something like this:
Controller A,B,C -> ObjectMapper X (ignore null value)
Controller D -> ObjectMapper Y (ignore empty value)
Default (all other controller) -> ObjectMapper Z (return null value)
Requirement Note:
I can't change the POJO since it's autogenerated, and I don't want to update the codegen lib or the mustache template for this.
Specific content negotiation is not an option
In addition to your ObjectMapper annotated with #Primary
you can configure more ObjectMappers qualified with bean names of your choice.
#Bean
#Primary
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
#Bean("mySpecialObjectMapper")
public ObjectMapper anotherObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper()
/* do some other configuration */;
return objectMapper;
}
Then you can refer to them in your controllers like this:
Just giving #Autowired will inject the primary ObjectMapper.
Giving #Autowired together with #Qualifier("anyName") will inject
the ObjectMapper configured with #Bean("anyName")
#Autowired
private ObjectMapper objectMapper;
#Autowired
#Qualifier("mySpecialObjectMapper")
private ObjectMapper otherObjectMapper;
Maybe you just need to use #JsonInclude(JsonInclude.Include.NON_NULL). Try to fix it in the response model level.
As we know, we can configure the global HttpMessageConverter by configureMessageConverters method in WebMvcConfigurer.
see https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-config-message-converters
But I want to configure a HttpMessageConverter for specified Controller to override the global configuration to implement the function different from the global.
How should I configure it? Can any friends give me pointers?
You can put below code in any of your configuration classes. and you have to autowire this specific objectmapper in that class where you need.Even you can create multiple objectmappers to serve different purposes.
#Bean
#Qualifier("customForController")
public ObjectMapper getObjectMapper() {
ObjectMapper mapper=new ObjectMapper();
return mapper;
}
#Bean
#Qualifier("customMessageConverter")
public MappingJackson2HttpMessageConverter converter() {
MappingJackson2HttpMessageConverter httConverter = new MappingJackson2HttpMessageConverter();
httConverter.setObjectMapper(getObjectMapper());
//others configuration goes here
return httConverter;
}
How to register newly introduced Corda RPC ObjectMapper in Spring Boot?
Even after having below code in #Configuration class Jackson failing to serialize Party object to JSON string.
#Bean
public JsonComponentModule jsonComponentModule() {
return new JsonComponentModule();
}
#Bean
#Primary
public ObjectMapper cordaRpcObjectMapper(NodeRPCConnection rpc) {
ObjectMapper objectMapper = JacksonSupport.createDefaultMapper(rpc.getProxy(), new JsonFactory(), true);
objectMapper.registerModule(jsonComponentModule());
return objectMapper;
}
After some tweaks I'm successfully able to register Corda RPC ObjectMapper with Jackson with below code.
//Register any other custom (de)Serializer classes.
#Bean
public Module jsonComponentModule() {
return new JsonComponentModule();
}
//Force Spring/Jackson to use only provided Corda ObjectMapper for serialization.
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(#Autowired NodeRPCConnection rpcConnection) {
ObjectMapper mapper = JacksonSupport.createDefaultMapper(rpcConnection.getProxy()/*, new JsonFactory(), true*/);
mapper.registerModule(jsonComponentModule());
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(mapper);
return converter;
}
The scenario is as follows. I have an ObjectMapper (Jackson 2) that registers a JodaModule, capable of serializing and de-serializing Joda DateTime type. This ObjectMapper is tested with custom JSON strings and works as expected.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JodaModule());
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+1:00"));
objectMapper.setDateFormat(new ISO8601DateFormat());
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
I have an RestTemplateFactory which is responsible for instantiating a RestTemplate, and it sets the previously configured ObjectMapper bean to the RestTemplate.
#Configuration
public class RestTemplateFactory {
#Autowired
private ObjectMapper objectMapper;
#Bean
public RestTemplate createRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
MappingJackson2HttpMessageConverter jsonMessageConverter = new MappingJackson2HttpMessageConverter();
jsonMessageConverter.setObjectMapper(objectMapper);
messageConverters.add(jsonMessageConverter);
// restTemplate.setMessageConverters(messageConverters); // This line was missing, but needs to be here. See answer.
return restTemplate;
}
}
Now when I contact the webservice it fails to de-serialize the DateTime object with the following error message:
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON: Can not instantiate value of type [simple type, class org.joda.time.DateTime] from String value; no single-String constructor/factory method
Also the DateTimeDeserializer.class is never called. Anyone has an idea what I am missing here?
OK, I was missing this line in my createRestTemplate() method.
restTemplate.setMessageConverters(messageConverters);
Add dependency
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
<version>2.9.0.pr4</version>
</dependency>
and use DateTimeDeserializer.class for deserializing as below
#JsonDeserialize(using = DateTimeDeserializer.class)
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd.MM.yyyy", timezone = "Europe/Berlin")
private DateTime date;
works fine for me. No need to add a custom message convertor.