I want to configure Jackson so that it automatically deserializes using constructors, without needing annotations. With Spring Boot, this works out of the box for most constructors but not single argument constructors.
Jackson 2.12 has released a configuration option to enable deserialization for single argument constructors as well:
ObjectMapper mapper = JsonMapper.builder()
.constructorDetector(ConstructorDetector.USE_PROPERTIES_BASED)
.build()
However, this doesn't use the usual Feature enabling/disabling interface. How can I set this with Spring Boot?
I don't want to lose any Spring Boot autoconfiguration that is being applied to the ObjectMapper bean.
I can't define a Jackson2ObjectMapperBuilder bean because, as of Spring Boot 2.4.1, this has not yet been updated to allow constructorDetector to be set.
Defining a bean of either Jackson2ObjectMapperBuilder or ObjectMapper will prevent application of any auto-configuration for these beans, as documented.
Instead, you can define a bean of type Jackson2ObjectMapperBuilderCustomizer which is a lambda that lets you call additional methods on the Spring Boot auto-configured Jackson2ObjectMapperBuilder.
Additionally, Jackson2ObjectMapperBuilder has the method postConfigurer which is another call back which lets you call methods on the auto-configured ObjectMapper.
Putting these together:
#Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> builder.postConfigurer(mapper ->
mapper.setConstructorDetector(USE_PROPERTIES_BASED)
);
}
Related
We are currently trying to implement a JSON Logging library making use of spring auto configuration or create its Jackson ObjectMapper. Our aim is to not override the spring auto configuration in class JacksonAutoConfiguration so that every customization by clients of the logging library won't be disabled.
The actual spring behavior is bean based and our main problem is that the JacksonProperties are not customizable and reusable for us. If we actually add a second bean of JacksonProperties the application start up would fail because JacksonAutoConfiguration.Jackson2ObjectMapperBuilderCustomizerConfiguration.class won't be able to handle a second bean. (The Spring Boot internal one is not annotated as #Primary.)
So what we did was start reimplementing every bean like the builder, customizer and so on. But this is not very maintainable as it duplicates framework code.
Our question now is if there would be any way to adapt the way of creating data sources for jackson object mapper beans. An example of creating data sources would be a following one.
#Bean(name = "testDataSource")
#ConfigurationProperties(prefix = "test.datasource")
public HikariDataSource naeDataSource(DataSourceProperties testDataSourceProperties) {
return testDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
We know the problem would still be that overriding the object mapper would deactivate it but if you pay attention to the application context it would be much easier to offer multiple object mapper instances.
Or is there any easier or other way we did not find so far?
Versions used:
Spring-Boot 2.0.5.RELEASE
UPDATE
I forked the spring boot project, made some changes to Spring Boot Autoconfiguration module and also set up a small demo project. I do not think it is the perfect way but with this changes it would be possible to initialize own object mapper easily from configuration properties. For example you can now easily create five object mapper for five different rest templates and clients called via REST-API.
#ConfigurationProperties(prefix = "logging.jackson")
#Bean("loggingJacksonProperties")
public JacksonProperties loggingJacksonProperties() {
return new JacksonProperties();
}
#Bean
public ObjectMapper secondObjectMapper(#Qualifier("loggingJacksonProperties") JacksonProperties loggingJacksonProperties) {
return loggingJacksonProperties.initializeJackson2ObjectMapperBuilder().build();
}
Comparing-Fork: https://github.com/spring-projects/spring-boot/compare/2.1.x...mixaaaa:feature/jackson_properties_initializer
Demo-Project: https://github.com/mixaaaa/jackson-demo
i wanna make request scoped configuration for Jackson serializer based on request parameter, so my problem is the configuration runs in a different thread and only runs when the app stars, how i can make java reconfigure Jackson serializer in every request ?
It's not possible to have request scoped config. Instead make the Jackson serializer bean request scoped and return instance with necessary config.
You can use approach described here
instanciation of a request scoped bean
#Configuration
public class MyBeansConfig {
#Bean
#Scope(value="request", proxyMode=ScopedProxyMode.TARGET_CLASS)
public RequestScopedBean requestScopedBean() {
//if needed set the required parameters
return new RequestScopedBean();
}
}
Just return here your serializer instance. Then just autowire it wehre it's necessary
I am implementing Custom Converter in Spring so my beans can convert from java.util.Date to java.time.LocalDateTime. I have implemented Converter already (by implementing Spring Converter interface)
Here is bean definition in #Configuration class
#Bean
ConversionService conversionService(){
DefaultConversionService service = new DefaultConversionService();
service.addConverter(new DateToLocalDateTimeConverter());
return service;
}
My question is : shall I pass my custom converter as Java Object or Spring Bean to service.addConverter?
In general what are the guidelines (criterias) whether to bean or not to bean in such scenarios?
Making an object a Spring Bean makes sense as you want that this object may benefit from Spring features (injections, transaction, aop, etc...).
In your case, it seems not required.
As conversionService is a Spring bean singleton that will be instantiated once, creating during its instantiation a plain java instance of DateToLocalDateTimeConverter seems fine : new DateToLocalDateTimeConverter().
Now, if later you want to inject the DateToLocalDateTimeConverter instance in other Spring beans, it would make sense to transform it to a Spring Bean.
For information Spring provides already this utility task in the Jsr310Converters class (included in the spring-data-commons dependency) :
import static java.time.LocalDateTime.*;
public abstract class Jsr310Converters {
...
public static enum DateToLocalDateTimeConverter implements Converter<Date, LocalDateTime> {
INSTANCE;
#Override
public LocalDateTime convert(Date source) {
return source == null ? null : ofInstant(source.toInstant(), ZoneId.systemDefault());
}
}
...
}
You could directly use it.
If you intend to inject this as a dependency of some kind into your application, and/or you intend to reuse it in multiple places, then it makes sense to register it as a bean. If you're not, then newing an instance up is acceptable.
Dependency injection and inversion of control are just that - how you inject dependencies into your app, and an acknowledgment that you no longer control how that's instantiated. Should you desire either of these, beans are suitable; if you don't, then new it up.
In you simple case, it does not seem to be necessary to add DateToLocalDateTimeConverter as a spring bean.
Reasons to add DateToLocalDateTimeConverter as a spring bean:
If it would make the implementation of conversionService() more readable (not the case in the question example)
You need the DateToLocalDateTimeConverter in other beans
The implementation of DateToLocalDateTimeConverter itself would need to have Spring beans injected, i.e. using #Autowired
In Jersey, how can I automatically add root in JAXB pojo?
I have my JAXB pojo
class Product {
private String name;
....
}
By default the generated json is
{
"name": "Burton Custom Freestlye 151",
}
I would like it to be
{
"product": {
"name": "Burton Custom Freestlye 151",
}
}
Note: What I mean by automatic is not creating separate class just to enclose another pojo.
This should be in serialization/deserialization into JSON.
Also I have other JSON don't use root element.
With Spring Boot, you can just configure an ObjectMapper as a Spring bean, and Spring Boot is set up where Jersey will use the mapper. The configuration property for the ObjectMapper to automatically add a root element is
SerializationFeature.WRAP_ROOT_VALUE
The default behavior is to take the class name and lower case it. If you want something different you can annotation the class with #XmlRootElement("newName") or #JsonRootName("newName"). To make a Spring bean just add the following in your configuration class
#Bean
public ObjectMapper mapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_NAME, true);
return mapper;
}
Note I think this (creating a Spring bean for the ObjectMapper) is only available starting from 1.4.0. If you are using a earlier version of Boot, you should just do this.
Also if you are expecting incoming JSON with the wrapped value, you will also want to unwrap it. You can do that with
DeserializationFeature.UNWRAP_ROOT_VALUE
Configure it the same way as above (passing true).
UPDATE
(Not very useful at this point, but maybe in the future)
Looking at the source for #JsonRootName, it seems Jackson has been planning on adding an alwaysWrap property in the annotation since 2.4. But I guess they have been delaying this. Not sure why. Maybe in the future it'll show up.
I'm trying to use MetricsModule to parse the response body of /metrics endpoint. For some reason the module never gets registered with the respective ObjectMapper.
I've found that this #PostConstruct method gets erased:
#PostConstruct
private void More ...registerModulesWithObjectMappers() {
Collection<Module> modules = getBeans(this.beanFactory, Module.class);
for (ObjectMapper objectMapper : getBeans(this.beanFactory, ObjectMapper.class)) {
objectMapper.registerModules(modules);
}
I've implemented my own code to do the same thing, but I'm not sure if this is the correct way to fix this issue.
Why wouldn't JacksonAutoConfiguration support this type of auto-configuration?
That method was removed in commit "Don't register Jackson Module beans with all ObjectMappers" for issue "Make Jackson auto-configuration more consistent and predictable". In short, it was done to not mess up with every ObjectMapper bean in the container.
To ensure that your Module bean gets registered with auto-configured ObjectMapper, make sure Module creation does not depend on an instance of ObjectMapper in any way. For example, do not autowire auto-configured ObjectMapper anywhere before Module bean is created.