I'm having trouble with my Websphere Liberty installation/IBM Bluemix instance. I'm using the new java.time classes (e.g. LocalDateTime) but the deserialized version using the Jackson 2.8 mapper results in badly handable results (full object deserialization instead of timestamps).
https://geowarin.github.io/jsr310-dates-with-jackson.html
But how can I do the same in Liberty?
e.g.: receive 2016-02-02T23:24:08.255+01:00 as the result in a deserialized JSON response
Thanks
The Liberty JAX-RS 2.0 feature includes Jackson to do basic serialization of Java Classes to JSON, but it doesn't expose any of the Jackson APIs.
If you want to change Jackson's behaviour, you should bundle these libraries in your application so that Liberty uses the Jackson that you've provided, rather than the one it has internally:
Jackson
Jackson providers
Then you can also include the Jackson JSR310 module to get your Java 8 times serialized as you want.
However, according to the documentation on that page, you need to register the module with your object mapper.
You want to call JacksonJsonProvider.setMapper() but this isn't straighforwards as usually this object gets instantiated automatically by Liberty and you never see it.
Instead, you can create a ContextProvider to tell the Jackson JAX-RS provider what ObjectMapper to use when it gets instantiated by Liberty.
Adapting the example from your link, you could write something like this:
#Provider
public class JSR310MapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> type) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return mapper;
}
}
That should create a configured mapper which should then be used by Jackson to serialize your Java 8 time objects.
Update: If you're overriding getClasses() in your Application class, then the JacksonJsonProvider and your JSR310MapperProvider won't be automatically discovered. You'll need to include both of these classes in the set you return from getClasses()
Related
i am using spring-boot 3.0.2 and have created a custom jackson serializer and registered it using #JsonComponent. i have confirmed it is registered in the ObjectMapper instance that gets injected using #Autowire. when i go to invoke the rest endpoint that will return an object that should be handled by the custom serializer, it is never by the RequestResponseBodyMethodProcessor.
after doing some digging it seems only the default set of converters created in WebMvcConfigurationSupport are being used and nothing else. i have tried registering my own instance of MappingJackson2HttpMessageConverter as a #Bean but it is never used.
the only way i have found to fix this issue is to replace whatever default is being created using extendMessageConverters in WebMvcConfigurer.
can anyone offer any suggestions? from the various blog posts i've read, this should just work but appears not to.
i expect json that looks like this:
["fqdn":"test.com"]
but instead get:
["fqdn:{"idnEncoded":false,"domainName":"test","tldName":"com"}]
I use SpringBoot v2.2.2RELEASE (very old version) and I configured a custom Module as follow:
#Configuration
public class JacksonConfig {
#Bean
public Module jacksonModule() {
SimpleModule module = new SimpleModule();
module.addSerializer(Object.class, new MyCustomSerializer());
return module;
}
}
This works well and it does what it supposed to do.
Recently, I've upgraded to the lates SpringBoot 2.7.0 and the serializer gets ignored! I can see that when my app loads then it instantiates a new Module instance, but it doesn't call the serializer anymore...
(What the serializer does is add a root node to the response (json) that returns to the client via REST Controller).
Any idea?
EDIT1
I tried to debug it by put a breakpoint in JacksonAuthConfiguration#configureModules and I saw the module in the list as well as some other Swagger related modules.
So if I understand it correct your problem is that Spring doesnt take this ObjectMapper for the RestController. And in this case I struggled with this problem aswell. I tried everything from defining a primary bean for ObjectMapper, defining a bean for the factor adding the module as you did but nothing worked.
So in the end my solution was to use #JsonDeserialize and #JsonSerialize annotations.
I have a small module that should only contain the resource model of my REST service. I want to create a test in this module to ensure that the resource model serializes and deserializes appropriate HAL format.
I have a single test and this is the configuration:
#RunWith(SpringRunner::class
#SpringBootTest
#AutoConfigureJsonTesters
class MyTest {
#Autowired
private lateinit var collectionTester: JacksonTester<Resources<Entity>>
....
}
and a very simple configuration
#SpringBootConfiguration
class TestConfig
When calling collectionTester.write on a list of Entity (which extends ResourceSupport) I don't get an _embedded field, instead I get
{"links":[],"content":[...]}
which is not HAL format.
How can I force #AutoConfigureJsonTesters to give me a JacksonTester with an ObjectMapper configured for HAL?
Spring Boot 2.0.0.RELEASE
Thanks!
The auto-configured JacksonTester will use the context’s ObjectMapper which won’t have any of the Spring HATEOAS stuff configured on it. You might be better creating a JacksonTester yourself and passing it an appropriately configured ObjectMapper to use.
I believe Spring HATEOAS has a module that it applies to the ObjectMapper to configure it. If you get stuck with that, asking in gitter/spring-projects/spring-data is probably your best bet as Spring HATEOAS is maintained by the Data team due to it being used by Spring Data REST.
The JacksonAutoConfiguration initializes an ObjectMapper with the JSR310Module registered. Then HttpMessageConvertersAutoConfiguration initializes a MappingJackson2HttpMessageConverter. But this MessageConverter never gets plugged in the RequestResponseBodyMethodProcessor.
As a result:
If the controller returns a object which should be jsonified by Jackson, dates are poorly converted because the wrong MappingJackson2HttpMessageConverter is used (initialized in WebMvcConfigurationSupport#addDefaultHttpMessageConverters)
But, if the ObjectMapper is injected in the controller and used to jsonify the same object, then the date format is alright.
How can I can wake the WebMvcAutoConfigurationAdapter up? What should I do to bind the configured Jackson ObjectMapper with Spring MVC and make it handle response bodies?
If you use #EnableWebMvc that would explain your symptoms So the rule thumb is: do not use #EnableWebMvc with Spring Boot, unless you wan't to customize Web MVC configuration?
I understand how to configure spring-boot and it provides a mature and sensible overriding mechanism that is documented well but I'm converting an application that gets its configuration from other source that is not in line with the spring-boot mechanism.
Ultimately the application makes properties available to the code that can be bound in using #Value("$the.prop.key:default") or used in spring xml config. The way that these properties are retrieved and bound cannot be changed.
I am trying to configure the embedded tomcat server port but the only way I can do this is using application.properties. I can change this to a different file and even change the location but I cannot change the mechanism (it has to be a file).
Looking into the spring-boot code I see it uses the notion of EmbeddedServletContainerCustomizer implementations to set these properties. Fine, I will create an implementation and set the server properties using this. But unfortunately you get 2 implementations trying to do the same thing ServerProperties and my implementation. The code orders these but because ServerProperties has not ordering it is set to the lowest priority and a low priority gets executed last and so my implementation gets overwritten.
Instead I have implemented a BeanPostProcessor:
#Named
public class SpringBootCustomConfigurator implements BeanPostProcessor {
#Value("$the.prop.key:8080")
private int port;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ServerProperties) {
ServerProperties serverProperties = (ServerProperties) bean;
serverProperties.setPort(port);
}
return bean;
}
}
This does what I need to do but it isn't a satisfactory implementation. Any thoughts?
Given that it's about a new source of external properties, I think it would be more natural to write an ApplicationContextInitializer (or ApplicationListener to listen for one of the Spring Boot events on startup) that adds a new PropertySource to your Environment in the right place. You can register initalizers with the SpringApplication or using META-INF/spring.factories.