InvalidFormatException for Date - fixing without using JsonFormat or modifying original class - spring-boot

Introduction
We are using a custom starter hosted on a nexus repository, that contains spring-cloud-feign clients that make requests to microservices.
One of the microservices returns the dates as "dd-MM-yyyy HH:mm:ssZ" and this works in most of our applications. However, we have one application that is throwing the following error:
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2019-10-16 14:23:17": not a valid representation (error: Failed to parse Date value '2019-10-16 14:23:17': Unparseable date: "2019-10-16 14:23:1
7")
Current work-around
My Current work-around, as I don't want to pollute the starter, is to extend the class and create a local feign-client and local pojo with the proper JsonFormat:
public class DocumentMetaDataFix extends DocumentMetaData {
#JsonFormat(
shape = Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss"
)
private Date creationDate;
#JsonFormat(
shape = Shape.STRING,
pattern = "yyyy-MM-dd HH:mm:ss"
)
Failed Fixes
I have tried the following in my configuration class, in order to try affecting the de-serialization from another path. However, the DocumentMetaDataSerializer is never called. The ObjectMapper bean IS called.
#Configuration
#EnableSpringDataWebSupport
#RequiredArgsConstructor
public class MyConfig extends WebMvcConfigurerAdapter {
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.deserializerByType(DocumentMetaData.class, new DocumentMetaDataDeserializer());
}
};
}
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.setDateFormat(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"));
//mapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, true);
return mapper;
}
#Bean
public Module dynamoDemoEntityDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(DocumentMetaData.class, new DocumentMetaDataDeserializer());
return module;
}
public static class DocumentMetaDataDeserializer extends JsonDeserializer<DocumentMetaData> {
#Override
public DocumentMetaData deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// return DynamoDemoEntity instance;
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
public DocumentMetaData deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer t) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
}
Full Stacktrace
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.util.Date` from String "2019-10-16 14:23:17": not a valid representation (error: Failed to parse Date value '2019-10-16 14:23:17': Unparseable date: "2019-10-16 14:23:1
7")
at [Source: (ByteArrayInputStream); line: 1, column: 580] (through reference chain: eu.europa.ec.nova.documentstore.DocumentMetaData["creationDate"])
at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1548)
at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:910)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:524)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseDate(StdDeserializer.java:467)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateBasedDeserializer._parseDate(DateDeserializers.java:195)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:285)
at com.fasterxml.jackson.databind.deser.std.DateDeserializers$DateDeserializer.deserialize(DateDeserializers.java:268)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:127)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3084)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237)
... 70 common frames omitted
So, any ideas?
I have searched through the project for references to Jackson in case there is anything else in my project causing this.
I am will try to go inside the ObjectMapper and try to debug the current parameters/fields of the configuration at ObjectMapper.java:3084 from the stacktace:
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3084)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:237)
... 67 common frames omitted
Update
I added a breakpoint in the objectmapper constructor, and am seeing that it is being initialized from more than one location. This led me to suspect that spring-boot is not using my ObjectMapper. Instead it is using an internal spring one that is called from MappingJackson2HttpMessageConverter .
<init>:480, ObjectMapper
build:606, Jackson2ObjectMapperBuilder
<init>:59, MappingJackson2HttpMessageConverter
<init>:74, AllEncompassingFormHttpMessageConverter
I will therefore try to over-ride this internal spring one, based on results I found from: How to customise the Jackson JSON mapper implicitly used by Spring Boot?
However this also failed.
References
Is it possible to configure Jackson custom deserializers at class level for different data types?
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper
https://www.baeldung.com/jackson-deserialization
very useful: https://mostafa-asg.github.io/post/customize-json-xml-spring-mvc-output/
How to customise Jackson in Spring Boot 1.4
Update - final list of tries
It still fails with an error.
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
builder.serializationInclusion(Include.NON_EMPTY);
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
//converters.add(cmsaMessageConverter());
converters.add(new StringHttpMessageConverter());
converters.add(new FormHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());
}
#Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.deserializerByType(DocumentMetaData.class, new DocumentMetaDataDeserializer());
}
};
}
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.setDateFormat(new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"));
//mapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, true);
return mapper;
}
#Bean
public Module dynamoDemoEntityDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(DocumentMetaData.class, new DocumentMetaDataDeserializer());
return module;
}
public static class DocumentMetaDataDeserializer extends JsonDeserializer<DocumentMetaData> {
#Override
public DocumentMetaData deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
// return DynamoDemoEntity instance;
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
public DocumentMetaData deserializeWithType(JsonParser jp, DeserializationContext ctxt, TypeDeserializer t) throws IOException {
JsonNode node = jp.getCodec().readTree(jp);
return null;
}
}
It still fails with an error.

Try using LocalDateTime,
this is what I'm doing and working for me
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime date;

Related

LocalDateTime not serializing based on given serializer registered with JavaTimeModule

I'm facing an issue where Spring boot (v2.6.13) is not parsing LocalDateTime based on a registered serializer, the response of LocalDateTime of RestController is always an array of integers.
#Bean
public Module javaTimeModule() {
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(new CustomLocalDateTimeSerializer());
return module;
}
class CustomLocalDateTimeSerializer extends StdSerializer<LocalDateTime> {
private static DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd");
protected CustomLocalDateTimeSerializer() {
super(LocalDateTime.class);
}
#Override
public void serialize(
LocalDateTime localDateTime,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeString(localDateTime.format(formatter));
}
}
Notes:
Injecting object mapper and serialize the object returns the correct format.
I've defined an object mapper annotated with #Primary, but still facing the same issue.
I want to configure everything globally - don't wanna use #JsonSerialize on each attribute-
it seems like Spring is using a different object mapper for serializing a method returned object.
I've found the issue and resolved it.
I have a WebMvcConfigurationSupport configuration which was overriding the configured object mapper.
I've solved it using following code
private final ObjectMapper objectMapper;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
{
var converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
converters.add(converter);
super.configureMessageConverters(converters);
}

How to connect Redis and Couchbase in spring

How can I connect redis and couchbase to my spring application.
I get this error Parameter 0 of method couchbaseMappingContext in org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration required a single bean, but 2 were found: - couchbaseCustomConversions: defined by method 'customConversions' in class path resource [{classPath}/chat/config/CouchbaseConfiguration.class] - redisCustomConversions: defined in null
I only need redis to 'look' at one package and the other ones need to be connected with only couchbase.
Redis config
#Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter(mapper);
return converter;
}
#Bean
public RedisTemplate<Long, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Long, ?> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Add some specific configuration here. Key serializers, etc.
return template;
}
Couchbase config
#EnableCouchbaseRepositories
#Configuration
public class CouchbaseConfiguration extends AbstractCouchbaseConfiguration {
#Value("${spring.data.couchbase.bucket-name}")
private String bucketName;
#Value("${spring.couchbase.username}")
private String username;
#Value("${spring.couchbase.password}")
private String password;
#Value("${spring.couchbase.connection-string}")
private String connectionString;
#Override
public String getConnectionString() {
return this.connectionString;
}
#Override
public String getUserName() {
return this.username;
}
#Override
public String getPassword() {
return this.password;
}
#Override
public String getBucketName() {
return this.bucketName;
}
}
and when I first start my app in terminal there is this info : Spring Data Redis - Could not safely identify store assignment for repository candidate interface
To resolve the ambiguity in taking the customConversions bean, we could tell the couchbase configuration class how to create the customConversions bean. Adding the below code to the class which extends AbstractCouchbaseConfiguration should solve the issue
#Bean
public CustomConversions customConversions() {
return super.customConversions();
}

Hibernate Anntotaion #Converter need to be configure before Spring Anntotation #propertySource

I am using #converter (Hibernate )to convert pojo in encrypted format which is from hibernate but key are placed in property file which would not be resolve by #propertySource (Spring annotation)
is there any way to manage bean creation seq in above case.
Please find the below code snippet for Converter, I had created another bean from encryption/decryption, but you can create config bean for properties and read properties from there.
#Component
#Converter
#Configurable
public class HashMapConverter implements AttributeConverter<Map<String, Object>, String> {
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/*
* Define your application properties bean here and read the properties from
* there
*/
private static ConfigEncryptionKeyConverter configEncryptionKeyConverter;
#Autowired
public void initEncryptionKeyConverter(ConfigEncryptionKeyConverter configEncryptionKeyConverter) {
// Set your beans here.
HashMapConverter.configEncryptionKeyConverter = configEncryptionKeyConverter;
}
#Override
public String convertToDatabaseColumn(Map<String, Object> attribute) {
try {
return configEncryptionKeyConverter.convertToDatabaseColumn(OBJECT_MAPPER.writeValueAsString(attribute));
} catch (final JsonProcessingException e) {
throw new ApplicationErrorException(e.getLocalizedMessage());
}
}
#SuppressWarnings("unchecked")
#Override
public Map<String, Object> convertToEntityAttribute(String dbData) {
Map<String, Object> attribute = null;
if (dbData != null) {
try {
attribute = OBJECT_MAPPER.readValue(configEncryptionKeyConverter.convertToEntityAttribute(dbData),
Map.class);
} catch (final IOException e) {
throw new ApplicationErrorException(e.getLocalizedMessage());
}
}
return attribute;
}
}
Hope this will help.

spring test rest template : Could not extract response: no suitable HttpMessageConverter found for response type

I Get this exception
org.springframework.web.client.RestClientException: Could not extract
response: no suitable HttpMessageConverter found for response type [AnalyticsResponse] and content type [application/json;charset=UTF-8]
in my junit test cases only(Rest endpoints work fine) if my DTO contains a map with a user defined class as a key
#Data
public class AnalyticsResponse {
private List<Committer> commitersList; //OK
private Map<Committer , Long> comittersCommitsMap; // Problem
private Map<Date, List<CommitItem>> commitItemsTimeLineMap; //OK
}
If comittersCommitsMap field is removed, every thing goes fine
my test case code snippet:
ResponseEntity<AnalyticsResponse> analyticsResponse = testRestTemplate.getForEntity(ANALYSIS_CONTROLLER_BASE_URL+"analytics?repo-full-name=" + searchResponse.getBody().get(0).getFull_name() ,
AnalyticsResponse.class);
---update : Committer class
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Committer {
private String name;
private String email;
}
By default, the ObjectMapper cannot determine serialization and deserialization for a Map in which a key is not a String, you have to provide your custom implementation.
Implement KeyDeserializer and JsonSerializer for Committer and Date(as key for Map)
Configure ObjectMapper - register module with KeyDeserializer and KeySerializer(JsonSerializer)
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
// KeyDeserializer for Committer (simple without 'null' check)
#RequiredArgsConstructor
public static class CommitterKeyDeserializer extends KeyDeserializer {
private final ObjectMapper mapper;
#Override
public Object deserializeKey(final String key,
final DeserializationContext ctxt)
throws IOException, JsonProcessingException {
return mapper.readValue(key, Committer.class);
}
}
// KeyDeserializer for Date (simple without 'null' check)
#RequiredArgsConstructor
public static class DateKeyDeserializer extends KeyDeserializer {
private final ObjectMapper mapper;
#Override
public Object deserializeKey(final String key,
final DeserializationContext ctxt)
throws IOException, JsonProcessingException {
return mapper.readValue(key, Date.class);
}
}
// JsonSerializer for Committer (simple without 'null' check)
#RequiredArgsConstructor
public static class CommitterJsonSerializer extends JsonSerializer<Committer> {
private final ObjectMapper mapper;
#Override
public void serialize(Committer committer,
JsonGenerator jgen,
SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeFieldName(mapper.writeValueAsString(committer));
}
}
// JsonSerializer for Date (simple without 'null' check)
public static class DateJsonSerializer extends JsonSerializer<Date> {
#Override
public void serialize(Date date,
JsonGenerator jgen,
SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeFieldName(String.valueOf(date.getTime()));
}
}
// ObjectMapper configuration
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
// register module with custom serializers and deserializers
mapper.registerModule(new SimpleModule()
.addKeyDeserializer(
Committer.class,
new CommitterKeyDeserializer(mapper))
.addKeyDeserializer(
Date.class,
new DateKeyDeserializer(mapper))
.addKeySerializer(
Committer.class,
new CommitterJsonSerializer(mapper))
.addKeySerializer(
Date.class,
new DateJsonSerializer()));
return mapper;
}
// RestTemplate configuration
#Bean
public RestTemplate restTemplate(List<HttpMessageConverter<?>> converters) {
RestTemplate restTemplate = new RestTemplate();
// add spring's predefined converters
restTemplate.setMessageConverters(converters);
return restTemplate;
}
}
Note that in this simple implementation the key Committer in comittersCommitsMap represented as a String in JSON response (RestTemplate with this implementation works as well):
{
"commitersList": [
{
"name": "name",
"email": "email"
}
],
"comittersCommitsMap": {
"{\"name\":\"name\",\"email\":\"email\"}": 1
},
"commitItemsTimeLineMap": {
"1570929503854": [
{
"data": "data"
}
]
}
}

OffsetDateTime returns the datetime in wrong format

I use a Spring 4.3.4, a Jackson 2.8.6 and a Tomcat 8.5.11. I've some REST API:
#RestController
#RequestMapping(value = "/netadvice")
public class NetworkAdviceController {
...
#RequestMapping(
value = "/offsetDateTime",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public OffsetDateTime todayOffsetDateTime() {
return OffsetDateTime.now();
}
}
After call /netadvice/offsetDateTime I would like to receive the datetime in the following format: 2017-02-14T08:46:06.102Z but I receive something like this: 1487062454.957000000.
What did I wrong and how can I fix this issue?
The following code is my custom datetime serializer and deserializer:
#Configuration
public class JsonConfig {
public static final DateTimeFormatter FORMATTER = ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
#Bean
#Primary
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(OffsetDateTime.class, new OffsetDateTimeSerializer());
javaTimeModule.addDeserializer(OffsetDateTime.class, new OffsetdateTimeDeserializer());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
public class OffsetDateTimeSerializer extends JsonSerializer<OffsetDateTime> {
#Override
public void serialize(OffsetDateTime value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value.format(FORMATTER));
}
}
public class OffsetdateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {
#Override
public OffsetDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return OffsetDateTime.parse(p.getValueAsString(), FORMATTER);
}
}
}

Resources