Customise JSON date formatting of JSON for spring-mvc (non-boot)? - spring

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.

Related

MappingJacksonHttpMessageConverter cannot be resolved to a type

I am trying to upgrade Spring Application (not using Spring Boot) from 2.3.2.RELEASE to 5.3.20 (latest version as of today), I do see below code is breaking and
Code:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
ObjectMapper defaultMapper = new ObjectMapper();
defaultMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, true);
defaultMapper.configure(SerializationConfig.Feature.REQUIRE_SETTERS_FOR_GETTERS, true);
mappingJacksonHttpMessageConverter.setObjectMapper(defaultMapper);
converters.add(mappingJacksonHttpMessageConverter);
}
What's the replacement of import org.codehaus.jackson.map.ObjectMapper to jacksondata bind for below code?
ObjectMapper defaultMapper = new ObjectMapper();
defaultMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, true);
defaultMapper.configure(SerializationConfig.Feature.REQUIRE_SETTERS_FOR_GETTERS, true);
mappingJacksonHttpMessageConverter.setObjectMapper(defaultMapper);
I was able to do like below
com.fasterxml.jackson.databind.ObjectMapper objectMapper = new com.fasterxml.jackson.databind.ObjectMapper();
objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, true);
objectMapper.configure(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS, true);
mappingJacksonHttpMessageConverter.setObjectMapper(objectMapper);
The MappingJacksonHttpMessageConverter is for Jackson1, which isn't supported anymore with Spring 5, it was deprecated in Spring 4.
You will need to migrate to Jackson2 and use the MappingJackson2HttpMessageConverter instead.
Instead of constructing the ObjectMapper manually I strongly suggest using the Jackson2ObjectMapperBuilder to construct the ObjectMapper. It will automatically detect and register well known Modules and allows for easier configuration.
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.defaultViewInclusion(true)
.featuresToEnable(SerializationConfig.Feature.REQUIRE_SETTERS_FOR_GETTERS)
.build();
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(mapper);
converters.add(converter);
}
Finally use the constructor instead of the setter as that saves building an additional ObjectMapper (done in the default constructor of the MappingJackson2HttpMessageConverter and then throwing it away) saves a few CPU cycles.

Spring Jackson custom Deserializer does not called

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.

SpringBoot Rest Controller Supporting both xml and json with JAXB Pojos + Custom Http Message Converters + Swagger-UI

Getting following Swagger error when I introduced custom message converters
Unable to render this definition
The provided definition does not specify a valid version field.
Please indicate a valid Swagger or OpenAPI version field. Supported version fields are swagger: "2.0" and those that match openapi: 3.0.n (for example, openapi: 3.0.0).
The error is misleading, I guess its getting confused with the converters. Any Ideas to get it fixed?
I followed - swagger-ui not working with custom XML ObjectMapper (no luck)
Background:
I have generated pojos from xsd(s) through xjc. And I have a rest endpoint which needs to support both xml and json for request/response
We got it working by following [spring documentation][1] section: 22.16.12 Message Converters
Here is what I added in MyConfig
#Configuration
#EnableWebMvc
public class MyConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
converters.add(new MappingJackson2XmlHttpMessageConverter(xmlMapper()));
}
#Bean
#Primary
public ObjectMapper objectMapper() {
return new Jackson2ObjectMapperBuilder()
.modulesToInstall(new JaxbAnnotationModule())
.build();
}
#Bean
public XmlMapper xmlMapper() {
return new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.defaultUseWrapper(false)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.modulesToInstall(new JaxbAnnotationModule())
.createXmlMapper(true)
.build();
}
}
and my controller
// all the open api annotations //
#RequestMapping(value = "/run",
produces = {"application/json", "application/xml"},
consumes = {"application/json", "application/xml"},
method = RequestMethod.POST)
public ResponseEntity<MyResponse> run(#RequestBody MyRequest request) {
Ok its about which objectMapper. I had to clean up and remove '#primary' which made the trick in MyConfig
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
return new MappingJackson2HttpMessageConverter(new Jackson2ObjectMapperBuilder()
.modulesToInstall(new JaxbAnnotationModule())
.build());
}
#Bean
public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter() {
return new MappingJackson2XmlHttpMessageConverter(new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.defaultUseWrapper(false)
.serializationInclusion(JsonInclude.Include.NON_EMPTY)
.modulesToInstall(new JaxbAnnotationModule())
.createXmlMapper(true)
.build());
}

Corda RPC JacksonSupport.createDefaultMapper to use ObjectMapper in Spring client

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;
}

Configured ObjectMapper not used in spring-boot-webflux

I have mixins configured in my objectmapperbuilder config, using the regular spring web controller, the data outputted according to the mixins.
However using webflux, a controller with a method returning a Flow or Mono have the data serialized like if the objectmapper a default one.
How to get webflux to enforce an objectmapper configuration to be used ?
sample config:
#Bean
JavaTimeModule javatimeModule(){
return new JavaTimeModule();
}
#Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.mixIn(MyClass.class, MyClassMixin.class);
}
I actually found my solution by stepping through the init code:
#Configuration
public class Config {
#Bean
JavaTimeModule javatimeModule(){
return new JavaTimeModule();
}
#Bean
Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer(){
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.featuresToEnable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.mixIn(MyClass.class, MyClassMixin.class);
}
#Bean
Jackson2JsonEncoder jackson2JsonEncoder(ObjectMapper mapper){
return new Jackson2JsonEncoder(mapper);
}
#Bean
Jackson2JsonDecoder jackson2JsonDecoder(ObjectMapper mapper){
return new Jackson2JsonDecoder(mapper);
}
#Bean
WebFluxConfigurer webFluxConfigurer(Jackson2JsonEncoder encoder, Jackson2JsonDecoder decoder){
return new WebFluxConfigurer() {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(encoder);
configurer.defaultCodecs().jackson2JsonDecoder(decoder);
}
};
}
}
I translated the solution of #Alberto Galiana to Java and injected the configured Objectmapper for convenience, so you avoid having to do multiple configurations:
#Configuration
#RequiredArgsConstructor
public class WebFluxConfig implements WebFluxConfigurer {
private final ObjectMapper objectMapper;
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.defaultCodecs().jackson2JsonEncoder(
new Jackson2JsonEncoder(objectMapper)
);
configurer.defaultCodecs().jackson2JsonDecoder(
new Jackson2JsonDecoder(objectMapper)
);
}
}
Just implement WebFluxConfigurer and override method configureHttpMessageCodecs
Sample code for Spring Boot 2 + Kotlin
#Configuration
#EnableWebFlux
class WebConfiguration : WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
configurer.defaultCodecs().jackson2JsonEncoder(Jackson2JsonEncoder(ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)))
configurer.defaultCodecs().jackson2JsonDecoder(Jackson2JsonDecoder(ObjectMapper()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)))
}
}
Make sure all your data classes to be encoded/decoded have all its properties annotated with #JsonProperty even if property name is equal in class and json data
data class MyClass(
#NotNull
#JsonProperty("id")
val id: String,
#NotNull
#JsonProperty("my_name")
val name: String)
In my case, I was trying to use a customized ObjectMapper while inheriting all of the behavior from my app's default WebClient.
I found that I had to use WebClient.Builder.codecs. When I used WebClient.Builder.exchangeStrategies, the provided overrides were ignored. Not sure if this behavior is something specific to using WebClient.mutate, but this is the only solution I found that worked.
WebClient customizedWebClient = webClient.mutate()
.codecs(clientCodecConfigurer ->
clientCodecConfigurer.defaultCodecs()
.jackson2JsonDecoder(new Jackson2JsonDecoder(customObjectMapper)))
.build();
I have tried all the different solutions (#Primary #Bean for ObjectMapper, configureHttpMessageCodecs(), etc.). What worked for me at the end was specifying a MIME type. Here's an example:
#Configuration
class WebConfig: WebFluxConfigurer {
override fun configureHttpMessageCodecs(configurer: ServerCodecConfigurer) {
val encoder = Jackson2JsonEncoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
val decoder = Jackson2JsonDecoder(objectMapper, MimeTypeUtils.APPLICATION_JSON)
configurer.defaultCodecs().jackson2JsonEncoder(encoder)
configurer.defaultCodecs().jackson2JsonDecoder(decoder)
}
}

Resources