Java Spring Boot apply configuration for FasterXml Jackson library - java

I have Spring Boot project with Maven dependency: com.fasterxml.jackson.datatype
And I want to enable two properties ACCEPT_EMPTY_STRING_AS_NULL_OBJECT and FAIL_ON_READING_DUP_TREE_KEY.
But fail two enable them in two different ways:
1) application.yml
jackson:
serialization:
WRITE_DATES_AS_TIMESTAMPS: false
deserialization:
FAIL_ON_READING_DUP_TREE_KEY: true
2) Adding them as Configuration Bean
#Configuration
public class JacksonConfiguration {
#Autowired
private ObjectMapper objectMapper;
#PostConstruct
private void configureObjectMapper() {
objectMapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT );
objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY );
}
}
Neither one of this ways gave me desired effect. Could you please advice correct way how to do it?

I tried to use two options for FasterXml Jackson:
ACCEPT_EMPTY_STRING_AS_NULL_OBJECT to automatically map empty
strings to null values. And look like it is not working jet
properly, according to
https://github.com/FasterXML/jackson-databind/issues/1563 So I
solved problem with writing custom deserialiser.
FAIL_ON_READING_D‌​UP_TREE_KEY to enable Strict JSON validation, but the desired effect I get with JsonParser.Feature.STRICT_DUPLICATE_DETECTION.
So now I end up with two working solutions:
#Bean
public ObjectMapper objectMapper() {
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.enable(JsonParser.Feature.STRICT_DUPLICATE_DETECTION);
return objectMapper;
}
and application.yml
jackson:
serialization:
WRITE_DATES_AS_TIMESTAMPS: false
parser:
STRICT_DUPLICATE_DETECTION: true
I will use application.yml, of course, to keep configuration compact and in one place.
Thanks to #Michal Foksa I will accept your answer, because it is one of the ways how to configure ObjectMapper properly.

Create and configure ObjectMapper from scratch:
#Configuration
public class JacksonConfiguration {
#Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT )
.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY );
}
}

Related

Swagger generated code date issue "Java 8 date/time type java.time.OffsetDateTime not supported by default"

I have SpringBoot Java (server stub) code generated from a YAML API definition file which I coded in SwaggerHub. I use Open API 3.
I cannot get this generated code working, seems quite buggy.
The error I cannot fix is this one:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.OffsetDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: io.swagger.v3.oas.models.OpenAPI["components"]->io.swagger.v3.oas.models.Components["schemas"]->java.util.TreeMap["CancelData"]->io.swagger.v3.oas.models.media.ObjectSchema["properties"]->java.util.TreeMap["dateStamp"]->io.swagger.v3.oas.models.media.DateTimeSchema["example"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.13.1.jar!/:2.13.1]
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1300) ~[jackson-databind-2.13.1.jar!/:2.13.1]
at com.fasterxml.jackson.databind.ser.impl.UnsupportedTypeSerializer.serialize(UnsupportedTypeSerializer.java:35) ~[jackson-databind-2.13.1.jar!/:2.13.1]
I get it when I git the API docs URL.
localhost:8080/.../api-docs
I tried all suggestion which I could find on the web but nothing helps.
I think it's related to this field which I have in my YAML file.
dateStamp:
type: string
format: date-time
description: The creation date and time of this cancel transaction
example: "2022-01-28T05:03:57Z"
I tried registering JavaTimeModule and all that was suggested on the web. I don't understand this error. I don't know even if I am putting this in the right place. But this is the fix I tried.
#Bean
ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
I put it in the SwaggerDocumentationConfig.
In general, I don't understand why this generated code is so buggy.
https://github.com/FasterXML/jackson-modules-java8/issues/219
serialize/deserialize java 8 java.time with Jackson JSON mapper
How should I fix this?
I ran into a similar issue earlier today. Looking at the jackson-datatype-jsr310 term, I came across https://geowarin.com/correctly-handle-jsr-310-java-8-dates-with-jackson/
In it, it suggests adding
compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
which I did using this:
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:+'
What you can then do when creating your ObjectMapper is replace
#Bean
ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
with
#Bean
ObjectMapper objectMapper(Jackson2ObjectMapperBuilder objectMapperBuilder) {
objectMapperBuilder.createXmlMapper(false).build();
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper;
}
The reason, why #Bean ObjectMapper doesnt work, is because swagger dont use Spring context beans, but initiate mapper by itself statically as a singleton.
So you may get this mapper and configure it as you need by calling:
io.swagger.util.Json.mapper().registerModule(new JavaTimeModule());
Do it before swagger beans initialization.
I had the same error.
Remove the example (only for dates) which will be automatically generated.
dateStamp:
type: string
format: date-time
description: The creation date and time of this cancel transaction
example: "2022-01-28T05:03:57Z"
If you prefer to use example
Try to create a class like this, it worked for me
package org.openapitools.configuration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.spring.web.json.JacksonModuleRegistrar;
#Configuration
public class JacksonModuleConfiguration implements JacksonModuleRegistrar {
#Override
public void maybeRegisterModule(ObjectMapper mapper) {
mapper.registerModule(new JavaTimeModule());
}
}

Where is the place to register a Jackson Codec in Vertx 4.0 using custom Object Mapper

I am trying to convert my Quarkus vertex sample to pure Vertx 4.0 and encountered a problem.
In Quarkus, it is easy to customize the Jackson ObjectMapper to serialize or deserialize the HTTP messages.
#ApplicationScoped
public class CustomObjectMapper implements ObjectMapperCustomizer {
#Override
public void customize(ObjectMapper objectMapper) {
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
objectMapper.disable(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS);
JavaTimeModule module = new JavaTimeModule();
LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
objectMapper.registerModule(module);
}
}
And in Vertx, how to customize the ObjectMapper gracefully? My intention is registering a custom ObjectMapper instead of the built-in one, thus when using Json.encode, it will use my custom objectMapper instead.
In my Vertx sample, the Json.encode will use the built-in objectMapper to serialize the Java 8 DateTime to an int array instead of an ISO date string.
First, you need to add jackson-databind to your dependencies because Vert.x 4 does not bring transitively.
Then in your main method:
io.vertx.core.json.jackson.DatabindCodec codec = (io.vertx.core.json.jackson.DatabindCodec) io.vertx.core.json.Json.CODEC;
// returns the ObjectMapper used by Vert.x
ObjectMapper mapper = codec.mapper();
// returns the ObjectMapper used by Vert.x when pretty printing JSON
ObjectMapper prettyMapper = codec.prettyMapper();
Now you can configure both mappers

Two instances of ObjectMapper

Is there a way to create two instances of ObjectMapper for different purpose.
Modified ObjectMapper
#Component
class MyObjectMapper extends ObjectMapper{
public MyObjectMapper(){
super();
}
public MyObjectMapper(MyObjectMapper om) {
super(om);
}
#Override
public MyObjectMapper copy() {
return new MyObjectMapper(this);
}
}
Now use it as follows
#Autowired ObjectMapper objectMapper; //performs standard serialization
#Autowire MyObjectMapper myMapper; //i can add custom filters in thiis mapper.
I tried a similar setup but the custom mapper actually affects the original mapper all the rest controllers throw JSON parse error: Unrecognized field
Edit: Not sure if this point is very important but still adding it
Am using spring-boot-starter-json
And that's exactly where you should use #Qualifier annotation.
This annotation may be used on a field or parameter as a qualifier for candidate beans when autowiring. It may also be used to annotate other custom annotations that can then in turn be used as qualifiers.
OK. Combining with Answer from Aniket figured out what is wrong and still looking for some more explanation.
Instead of instantiating the ObjectMapper as new ObjectMapper(). Building it with Mapper fixed it.
So, two have multiple instance of ObjectMapper
#Primary
#Bean
public ObjectMapper objectMapper(){
return new Jackson2ObjectMapperBuilder()
.build();
}
#Bean("customMapper")
public ObjectMapper customMapper(){
ObjectMapper customMapper = new Jackson2ObjectMapperBuilder().build();
mapper.<your customization , filters, providers etc;>
return mapper;
}
The #Primary will be used by in all default cases i.e When you simply #Autowire or the default serialization applied to your Response/Request Body by your controller.
To use your Custom Mapper, explicitly use with the Bean ID.
#Autowired #Qualifier("customMapper") ObjectMapper mapper;

Spring Boot: Jackson won't pick up configuration from "application.properties"

I have a multi-module Maven-based Spring Boot Application. In one of my modules' application.properties file, I am setting Jackson to not serialize dates as timestamps (arrays), so that they are always serialized as strings (which is the fallback behavior). The property is this:
spring.jackson.serialization.write_dates_as_timestamps=false
Problem is... the property doesn't seem to be picked up by either Spring Boot/Jackson. The java.time.LocalDate instances I am trying to serialize always get written as timestamps. I have checked the code inside the LocalDateSerializer.serialize(...) method (from Jackson's own JavaTimeModule), and found this:
#Override
public void serialize(LocalDate date, JsonGenerator generator, SerializerProvider provider) throws IOException
{
if (useTimestamp(provider)) { // This always returns true
generator.writeStartArray();
generator.writeNumber(date.getYear());
generator.writeNumber(date.getMonthValue());
generator.writeNumber(date.getDayOfMonth());
generator.writeEndArray();
} else {
String str = (_formatter == null) ? date.toString() : date.format(_formatter);
generator.writeString(str);
}
}
The useTimestamp() method always returns true, no matter what my configuration is. :(
Maybe you can create a custom MappingJackson2HttpMessageConverter and an ObjectMapper. Here is an example configuration class,
#Configuration
public class MyConfiguration extends WebMvcConfigurerAdapter {
#Bean
public MappingJackson2HttpMessageConverter
getMappingJacksonHttpMessageConverter() {
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter();
...
ObjectMapper mapper = new ObjectMapper();
...
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
//or
// mapper.configure(
// SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
converter.setObjectMapper(mapper);
return converter;
}
}
Thanks to Indra's answer, I got an important clue that helped me figure out the issue.
Turns out that my Spring Boot application has multiple instances of RestTemplate in its application context (it is a pretty big system and relies upon some custom proprietary frameworks to work). Most of these RestTemplate instances were there just to support my system's architecture. There is only one RestTemplate instance that I should've cared about: The one that I wrote and allows my business logic to work (by enabling my system to consume remote RESTful APIs).
So, when I was trying to configure Jackson and its ObjectMapper, I wasn't really affecting my RestTemplate instance. By the time my configuration was read, that RestTemplate had long ago been initialized with the default settings provided by Spring Boot.
This is how my final configuration class looks:
#Configuration
public class RestConfiguration {
#Bean
public RestTemplate myRestTemplate(HttpMessageConverters customConverters) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(customConverters.getConverters());
return restTemplate;
}
/*
* The following custom HttpMessageConverter is injected into myRestTemplate
* in order to appropriately configure the serialization/deserialization of LocalDate
* instances into/from strings.
*/
#Bean
public HttpMessageConverters customConverters() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
converter.setObjectMapper(mapper);
return new HttpMessageConverters(converter);
}
}
What I am doing is to explicitly configure the RestTemplate instance that needed to be configured.
The following documentation was also very helpful: HttpMessageConverters. It describes how to declare custom HttpMessageConverters for use in the application context.

Jackson2ObjectMapperBuilder enable field visibility ANY

I'm using spring-boot and want to customize the ObjectMapper created.
What I want to do is be able to serialize objects that do not have a getter or setters. Before this could be done by putting JsonAutoDetect.Visibility.ANY on the ObjectMapper.
But how can I enable this feature using the Jackson2ObjectMapperBuilder bean I'm currently exposing ?
You can use a Jackson2ObjectMapperBuilder subclass that overrides the configure(ObjectMapper) method:
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return new Jackson2ObjectMapperBuilder() {
#Override
public void configure(ObjectMapper objectMapper) {
super.configure(objectMapper);
objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
}
};
}
If you want to keep the ObjectMapper configurable through the spring.jackson.* properties that Spring Boot provides, then you better don't define your own Jackson2ObjectMapperBuilder bean (check JacksonObjectMapperBuilderConfiguration inside JacksonAutoConfiguration class for details).
What you can do instead is this:
#Bean
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder mapperBuilder) {
return mapperBuilder.build().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
}
I spend a half of day to play with different settings.
So I manage to work it (1.3.2.RELEASE) when:
I configure jackson in simple #Configuration annotated config class (not extended from WebMvcConfigurerAdapter)
I have NOT#EnableWebMvc
Then Jackson2ObjectMapperBuilder objectMapperBuilder solution is
work, but spring.jackson.serialization.indent_output: true in properties ignored.
At last I finished with
#Autowired(required = true)
public void configeJackson(ObjectMapper objectMapper) {
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
}
But all this is puzzle for me. I wrote a question about any explanation of all this magic in order to have some undestanding and solve problem not by
trial-and-error method: Are there any Spring Boot documentation for understanding how web mvc configuration is work?

Categories