Jackson - configure override for collections via Jackson2ObjectMapperBuilderCustomizer - spring-boot

I am customizing treatment of collections in my Jackson's object mapper in my Spring Boot config by constructing a new mapper like so
#Configuration
public class Config {
#Autowired(required = true)
public void objectMapper(ObjectMapper mapper) {
mapper.configOverride(Collection.class).setInclude(JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, null));
mapper.configOverride(List.class).setInclude(JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, null));
mapper.configOverride(Map.class).setInclude(JsonInclude.Value.construct(JsonInclude.Include.NON_EMPTY, null));
}
While this works, I understand that a more elegant approach is to use Jackson2ObjectMapperBuilderCustomizer :
#Bean
public Jackson2ObjectMapperBuilderCustomizer customizeJackson2ObjectMapper() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder
.indentOutput(true)
.someOtherMethod(...)
}
};
}
How do I implement ObjectMapper collection tweaks above via Jackson2ObjectMapperBuilder ?

You can use a simple Module defined locally, like in this other use case. The SetupContext also has a configOverride() method, just like the ObjectMapper itself.

No idea ? I'm interested to do the same just to add :
mapper.configOverride(Map.Entry.class).setFormat(forShape(Shape.OBJECT));
Because #JsonFormat(shape = JsonFormat.Shape.OBJECT) doesn't work well ( https://github.com/FasterXML/jackson-databind/issues/1419 ) and but after Jackson 2.5 it is the only solution (but requires 2.9.x) to restore the previous behavior without writing a custom serializer.

Related

Can anyone tell me how can I output null to empty values using jackson mapper in spring Rest?

I have tried the solutions to above that I found on stackoverflow. But they didn't do what I wanted the controller to do. I don't want to change getter/setter method in every class to convert the null values to empty values. I am looking for a solution which will allow me to do this at global level using object mapper, by configuration perhaps.
Add this bean in your configuration class:
#Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(new NullSerializer());
objectMapper.registerModule(module);
builder.configure(objectMapper);
return builder;
}
NullSerializer class:
public class NullSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
// any JSON value you want...
gen.writeString("");
}
}
Thanks and this solution does not work...I have mentioned that I have tried the stack overflow solutions and they dont work. its not printing anything anymore even the values that are not null.

SET spring.jackson.serialization.write-dates-as-timestamps & date-format to ISO1806 globally

I know, there are several similar questions about that problem but I cant find any satisfying solution. Thats why I would like to reopen it here.
I want to set the JSON serialization of Dates (java.util) in SpringBoot to ISO1806 format globally.
I tried:
add dependecy:
com.fasterxml.jackson.datatype:jackson-datatype-joda
set property:
spring.jackson.serialization.write-dates-as-timestamps=false
spring.jackson.date-format=yyyy-MM-dd'T'HH:mm:ss'Z' e.g.
-didnt work. Still TimeStamps
Also tried some other solutions but they didnt work or were deprecated or hacked etc...
So how to solve it correctly with current state?
side note:
I cant user #JsonFormat() annotation since the classes are generated by older systems which I cant access right now. Thats why I seek for a global solution. Anyway a global soultion seems to be a logical one.
Hope someone can help.
Thanks in advance for any hints.
Kind Regards
Gregor
You can do it using Serialization. Just add this bean in your configuration class and write serialization class as per your requirement.
#Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Date.class, new CustomDateSerializer());
objectMapper.registerModule(module);
builder.configure(objectMapper);
return builder;
}
Serializer class:
public class CustomDateSerializer extends JsonSerializer<Date>{
#Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
String date = null;//parse here as per your requirement.
gen.writeString(date);
}
}
#Bean
#Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper;
}

Configure a Jackson's DeserializationProblemHandler in Spring environment [duplicate]

This question already has answers here:
Can't set ProblemHandler to ObjectMapper in Spring Boot
(2 answers)
Closed 4 years ago.
As I understood, Spring is already providing a bean for Jackson ObjectMapper. Therefore, instead of creating a new bean, I'm trying to customize this bean.
From this blog post, and then this Github project I used Jackson2ObjectMapperBuilder bean to achieve this customization.
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.findModulesViaServiceLoader(true);
return builder;
}
Then, I was trying to customize the deserializer in order to make it lenient: if an exception is raised when deserializing a property, I want the result object's property to be null and let the deserialization continue (default is to fail on first property that cannot be deserialized).
I've been able to achieve that with a class NullableFieldsDeserializationProblemHandler that extends DeserializationProblemHandler (I do not think the code is relevant but if needed, I can share it).
The simplest way to register this handler is to use the .addHandler() method of ObjectMapper. But of course, doing like this, I would need to set that every time I inject and use the ObjectMapper. I'd like to be able to configure handler so that every time the ObjectMapper is auto-wired, the handler is already present.
The best solution I came up with so far is to use a #PostConstruct annotation only to register the problem handler.
#Configuration
public class JacksonConfiguration implements InitializingBean {
#Autowired private ObjectMapper objectMapper;
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.findModulesViaServiceLoader(true);
return builder;
}
#Override
public void afterPropertiesSet() {
objectMapper.addHandler(new NullableFieldsDeserializationProblemHandler());
}
}
But the problem of this solution is that it seems I can still access an autowired ObjectMapper that doesn't have yet registered the problem handler (I can see it happening after when I need it in debug mode).
Any idea how I should register this handler? I've noticed Jackson2ObjectMapperBuilder has a .handlerInstantiator() but I couldn't figure out how to use it.
Note I've also tried with Jackson2ObjectMapperBuilderCustomizer since I'm using Spring Boot but had no better results.
It's not possible to directly add a DeserializationProblemHandler to the ObjectMapper via a Jackson2ObjectMapperBuilder or Jackson2ObjectMapperBuilderCustomizer. The handlerInstanciator() method is for something else.
However, it's possible to do so by registering a Jackson module:
the builder has a modules() method
the module has access via setupModule() to a SetupContext instance, which has a addDeserializationProblemHandler() method
This works:
#Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.modules(new MyModule());
}
};
}
private static class MyModule extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
// Required, as documented in the Javadoc of SimpleModule
super.setupModule(context);
context.addDeserializationProblemHandler(new NullableFieldsDeserializationProblemHandler());
}
}
What about writing a bean like this:
#Configuration
public class ObjectMapperConfiguration {
#Bean
ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// jackson 1.9 and before
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// or jackson 2.0
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
This is for global configuration. If, instead, what you want to do is to configure the feature for specific a class, use this annotation above the class definition:
#JsonIgnoreProperties(ignoreUnknown = true)

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

Upgrade from Spring Boot 1.3 to Spring Boot 1.4 and Pageable is not working as expected.

I am converting an existing Spring Boot application from 1.3.6 to 1.4.1. I would like to have a default page size for repository and controller responses of 25. I am not getting the expected behavior in either case. For repository methods I am getting a page size of 20. For controllers I am getting 0 for the page size.
I added a new configuration class to define the default page size. I found this code snippet in another article. The debug message does get printed out.
#Configuration
public class RestConfigurationAdapter extends WebMvcConfigurerAdapter {
private static final int DEFAULT_PAGE_SIZE = 25;
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
System.out.println("DEBUG: AddArguments----");
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
resolver.setFallbackPageable(new PageRequest(0, DEFAULT_PAGE_SIZE));
argumentResolvers.add(resolver);
super.addArgumentResolvers(argumentResolvers);
}
}
In a custom controller I would like to have a default pageable populated with a size of 25. However the pageable object is null in this controller. In 1.3.x the pageable object worked as expected.
public class BatchManagerController
{
#Autowired
private BatchRepository batchRepository;
#Autowired
private PagedResourcesAssembler pagedResourcesAssembler;
#Transactional(readOnly = true)
#RequestMapping(value = "/search/managerBatchView", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#PreAuthorize("hasRole(T(com.nextgearcapital.tms.api.util.AuthorityEnum).MANAGER)")
public ResponseEntity<?> getManagerBatchListView(BatchListSearchRequest requestDTO, Pageable pageable, PersistentEntityResourceAssembler myAssembler)
{
System.out.println("DEBUG1:---------- " + pageable);
Page<Batch> batchPage = batchRepository.findBatchesForManager(requestDTO, pageable);
PagedResources<VaultResource> pagedResources = pagedResourcesAssembler.toResource(batchPage, myAssembler);
return new ResponseEntity<>(pagedResources, HttpStatus.OK);
}
}
When calling SDR Repository methods with a pageable parameter, the parameter works correctly, but it has a default page size of 20, rather than 25.
I would appreciate any help and advise in getting the correct configuration for pagination.
You probably have 2 solutions
Register the PageableHandlerMethodArgumentResolver as an #Bean which will disable the auto configuration for Spring Data Web.
Create a BeanPostProcessor to do additional configuration on the existing PageableHandlerMethodArgumentResolver.
Using #Bean
#Configuration
public class RestConfigurationAdapter extends WebMvcConfigurerAdapter {
private static final int DEFAULT_PAGE_SIZE = 25;
#Bean
public PageableHandlerMethodArgumentResolver pageableResolver() {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver();
resolver.setFallbackPageable(new PageRequest(0, DEFAULT_PAGE_SIZE));
return resolver;
}
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
System.out.println("DEBUG: AddArguments----");
argumentResolvers.add(pageableResolver());
}
}
Drawback is that it will disable the autoconfiguration for Spring Data Web, so you might miss some things.
Using a BeanPostProcessor.
#Bean
public BeanPostProcessor pageableProcessor() {
private static final int DEFAULT_PAGE_SIZE = 25;
return new BeanPostProcessor() {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof PageableHandlerMethodArgumentResolver) {
((PageableHandlerMethodArgumentResolver) bean).setFallbackPageable(new PageRequest(0, DEFAULT_PAGE_SIZE));
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
}
Drawback is that it is a little more complex as registering your own PageableHandlerMethodArgumentResolver instance as a bean. Advantage however is that you can simply use this to add additional configuration to existing beans and leave the auto configuration in tact.
Starting in spring-data-commons version 2.0, there is are 2 new classes that might make this kind of thing easier:
SortHandlerMethodArgumentResolverCustomizer
PageableHandlerMethodArgumentResolverCustomizer
Unfortunately that's not the version that ships with the current version (1.5.9) of Spring Boot, so replace at your own risk.
#Bean
PageableHandlerMethodArgumentResolverCustomizer pagingCustomizer() {
// p is PageableHandlerMethodArgumentResolver
return p -> p.setMaxPageSize(25);
}
In this case, one would probably call resolveArgument to manipulate it.
That said, I'm not sure spring-data-rest would use that config. There is a HateoasPageableHandlerMethodArgumentResolver which seems more likely that source of what I would think SDR would use. If that's the case, the BeanPostProcessor #M. Deinum suggested is probably your best option.
Spring Data Web Support

Resources