Unable to combine Annotations - java

I have a JSON String and I am using Jackson to parse it into a POJO. The incoming JSON string has a different format, so I had to write my own converter to convert the String to LocalTimeDate object. I use 2 annotations to achieve this.
public class Content {
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#TDateFormat
private LocalDateTime modifiedDate;
}
TDateFormat: This annotation specifies the date format we need. It also has a default value.
#Retention(RetentionPolicy.RUNTIME)
public #interface TDateFormat {
String value() default "MMM d, yyyy, hh:mm:ss a";
}
LocalDateTimeDeserializer: This holds the logic to deserialize. We use the TDateFormat value get the format to use. I have extracted the logic out to an Util class
#NoArgsConstructor
public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> implements ContextualDeserializer {
private String format;
public LocalDateTimeDeserializer(String format) {
this.format = format;
}
#Override
public JsonDeserializer<?> createContextual(DeserializationContext dc, BeanProperty bp) {
return new LocalDateTimeDeserializer(bp.getAnnotation(TDateFormat.class).value());
}
#Override
public LocalDateTime deserialize(JsonParser jp, DeserializationContext dc) throws IOException {
LocalDateTime localDateTime = Utils.convertDate(jp.getText(), format);
System.out.println("Done::: " + localDateTime);
return localDateTime;
}
}
The above code works fine. Now, I am trying to combine #TDateFormat and #JsonDeserialize(using = LocalDateTimeDeserializer.class) to #TDeserializer
#TDeserializer Looks like below.
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#TDateFormat
public #interface TDeserializer {
}
And I want to use the annotation like this
public class Content {
#TDeserializer
private LocalDateTime modifiedDate;
}
But I get the following error, when I make this little change.
2022-09-29 23:32:09.569 ERROR 90506 --- [ntainer#0-0-C-1] c.t.k.service.impl.KafkaMessageConsumer : Exception occurred::: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
at [Source: (String)"{"modifiedDate":"Sep 29, 2022, 11:25:56 PM" [truncated 149 chars]; line: 1, column: 52] (through reference chain: com.techopact.kafkautil.model.Article["modifiedDate"])
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
Can anyone please let me know what more I have to do to combine the annotations?

Related

Modelmapper failed to convert java.lang.String to java.sql.Timestamp

main() {
StudentDataDto source= new StudentDataDto();
studentDataDto.setCreatedAt("2022-01-20T11:12:46");
StudentMetaDataEntity destination= modelMapper.map(studentDataDto,
StudentMetaDataEntity.class);
}
StudentDataDto {
private String createdAt;
}
StudentMetaDataEntity {
private Timestamp createdAt; (java.sql.Timestamp)
}
Exception message:
org.modelmapper.MappingException: ModelMapper mapping errors:
1) Converter org.modelmapper.internal.converter.DateConverter#2b08772d failed to convert java.lang.String to java.sql.Timestamp.
Caused by: org.modelmapper.MappingException: ModelMapper mapping errors:
1) String must be in JDBC format [yyyy-MM-dd HH:mm:ss.fffffffff] to create a java.sql.Timestamp
1 error
at org.modelmapper.internal.Errors.toMappingException(Errors.java:258)
at org.modelmapper.internal.converter.DateConverter.dateFor(DateConverter.java:125)
at org.modelmapper.internal.converter.DateConverter.convert(DateConverter.java:70)
at org.modelmapper.internal.converter.DateConverter.convert(DateConverter.java:53)
at org.modelmapper.internal.MappingEngineImpl.convert(MappingEngineImpl.java:306)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:109)
at org.modelmapper.internal.MappingEngineImpl.setDestinationValue(MappingEngineImpl.java:245)
at org.modelmapper.internal.MappingEngineImpl.propertyMap(MappingEngineImpl.java:187)
at org.modelmapper.internal.MappingEngineImpl.typeMap(MappingEngineImpl.java:151)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:114)
at org.modelmapper.internal.MappingEngineImpl.map(MappingEngineImpl.java:71)
at org.modelmapper.ModelMapper.mapInternal(ModelMapper.java:573)
at org.modelmapper.ModelMapper.map(ModelMapper.java:406)
...
By referring to this similar question's answers, I understand that the source code of model mapper restrict the timestamp string formats.
My question:
Instead of changing my StudentDataDto property type to java.sql.Timestamp, is it possible that I keep my desired timestamp format in yyyy-MM-dd'T'HH:mm:ss and customize my modelmapper converter to solve the exception?
You just need to write you own converter and register it with ModelMapper instance.
Option 1 - more general. If your date string will always be in this format, you can write a converter from String to java.sql.Timestamp, so it will always be applied even when using other dtos.
public class StringToTimestampConverter implements Converter<String, Timestamp> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
#Override
public Timestamp convert(MappingContext<String, Timestamp> mappingContext) {
String source = mappingContext.getSource();
LocalDateTime dateTime = LocalDateTime.parse(source, this.formatter);
return Timestamp.valueOf(dateTime);
}
}
Basically convert string to LocaDateTime using DateTimeFormatter, then convert using Timestamp.valueOf(LocalDateTime).
Option 2 - more specific. If you are using different formats in your app you can make StudentDataDto to StudentMetaDataEntity converter
public class DtoToMetaConverter implements Converter<StudentDataDto, StudentMetaDataEntity> {
#Override
public StudentMetaDataEntity convert(MappingContext<StudentDataDto, StudentMetaDataEntity> mappingContext) {
StudentDataDto source = mappingContext.getSource();
StudentMetaDataEntity dest = new StudentMetaDataEntity();
//Convert string to timestamp like in other example
dest.setCreatedAt(...);
return dest;
}
}
Then register the converter and test it.
public class TimestampMain {
public static void main(String[] args) {
ModelMapper modelMapper = new ModelMapper();
modelMapper.addConverter(new StringToTimestampConverter());
StudentDataDto source = new StudentDataDto();
source.setCreatedAt("2022-01-20T11:12:46");
StudentMetaDataEntity destination = modelMapper.map(source, StudentMetaDataEntity.class);
System.out.println(destination.getCreatedAt());
}
}
This example uses the more general option 1, but if you need option 2, just register the other converter in similar fashion.

JSON deserialization problem - error parsing Instant

I need to persist object in Azure Table Storage. Problem is I don't want to use Date type in my Entity class. I would like to use Instant but I have problems with parsing with the JasonMappingException.
Here is my entity class:
#Data
public class Event extends TableServiceEntity {
#NotNull
#JsonDeserialize(using = JsonDateDeserializer.class)
private Instant eventStart;
#NotNull
#JsonDeserialize(using = JsonDateDeserializer.class)
private Instant eventEnd;
#NotBlank
private String eventName;
private String eventDescription;
//default constructor
public Event () {}
}
And here is my deserializer:
public class JsonDateDeserializer extends JsonDeserializer<Instant> {
#Override
public Instant deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
String date = jsonParser.getText();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
try {
return sdf.parse(date).toInstant();
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
I am getting this error:
2020-06-10 20:47:19.685 WARN 11745 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: java.text.ParseException: Unparseable date: "2020-06-12T08:00:00UTC"; nested exception is com.fasterxml.jackson.databind.JsonMappingException: java.text.ParseException: Unparseable date: "2020-06-12T08:00:00UTC" (through reference chain: com.komix.eventmanager.model.Event["eventStart"])]
I am soo tired of the date problems with Azure DB. Could you please help me?
EDIT: Ok, after some edit parsing is fine but application gives me following error:
java.lang.IllegalArgumentException: Type class java.time.Instant is not supported.
It seems that Azure Table Storage doesn't support Instant??! I can't even find any good documentation for Asure Table data types supported...
'Z' is considered here as constants. You need to pass z without the quotes.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
Doc ref here

Ignore RootNode and custom mapping in Jackson/spring/Java

How i can ignore rootname if I dont need it? I need only bill.
if I remove "version" from json work fine..
my error on console log
2019-07-27 19:20:14.874 WARN 12516 --- [p-nio-80-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Unexpected token (FIELD_NAME), expected END_OBJECT: Current token not END_OBJECT (to match wrapper object with root name 'bill'), but FIELD_NAME; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Unexpected token (FIELD_NAME), expected END_OBJECT: Current token not END_OBJECT (to match wrapper object with root name 'bill'), but FIELD_NAME
at [Source: (PushbackInputStream); line: 8, column: 2]]
my json look like this
{
"bill":
{
"siteId":"gkfhuj-00",
"billId":"d6334954-d1c2-4b51-bb10-11953d9511ea"
},
"version":"1"
}
my class for json
i try use JsonIgnoreProperties but its dont help also i write "version"
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonRootName(value = "bill")
public class Bill {
private String siteId;
private String billId;
//getters and setters
my post method lisen object Bill
#PostMapping("/bill")
#ResponseBody
public ResponseEntity<String> getBill(#RequestBody Bill bill)
As you are relying on the Spring boot through annotations and Jackson, custom deserializer will work flawless in here. You have to create the deserializer class as show below
public class BillDeserializer extends StdDeserializer<Bill> {
public BillDeserializer() {
this(null);
}
public BillDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Bill deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode billNode = jp.getCodec().readTree(jp);
Bill bill = new Bill();
bill.setSiteId(billNode.get("bill").get("siteId").textValue());
bill.setBillId(billNode.get("bill").get("billId").textValue());
return bill;
}
}
Now you have to instruct your Jackson to use this deserializer instead of the default one for the Bill class. This is done by registering the desearilizer. It can be done through a simple annotation on the Bill class like #JsonDeserialize(using = BillDeserializer.class)
Your Bill class will typically look as specified below
#JsonDeserialize(using = BillDeserializer.class)
public class Bill {
private String siteId;
private String billId;
//getters and setters
}

How to make Spring Data Elasticsearch work with java.time.LocalDateTime for date

I am using Spring Data support for Elasticsearch. Here is the timestamp field mapping:
#Field(type = FieldType.Date, index = FieldIndex.not_analyzed, store = true,
format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern ="yyyy-MM-dd'T'HH:mm:ss.SSSZZ")
private LocalDateTime timestamp;
This results in mapping of the field in Elasticsearch as follows:
"timestamp":{"type":"date","store":true,"format":"yyyy-MM-dd'T'HH:mm:ss.SSSZZ"}
When I use java.util.Date instead everything works fine. However, when I switch to java.time.LocalDateTime as above the document sent to Elasticsearch causes an exception. Here is the document (timestamp field only for brevity):
"timestamp": {
"hour":7, "minute":56, "second":9, "nano":147000000, "year":2017, "month":"FEBRUARY",
"dayOfMonth":13, "dayOfWeek":"MONDAY", "dayOfYear":44, "monthValue":2, "chronology": {
"id":"ISO", "calendarType": "iso8601"
}
}
And the exception:
MapperParsingException[failed to parse [timestamp]]; nested: IllegalArgumentException[unknown property [hour]];
(...)
Caused by: java.lang.IllegalArgumentException: unknown property [hour]
It looks like the pattern is being ignored here when jsonizing the document. Any possible tips? Or perhaps you might know how to use the "built-in" _timestamp field with Spring Data?
Check https://github.com/spring-projects/spring-data-elasticsearch/wiki/Custom-ObjectMapper to add JavaTimeModule to your ObjectMapper.
#Configuration
public class ElasticSearchConfiguration {
#Bean
public ElasticsearchTemplate elasticsearchTemplate(Client client) {
return new ElasticsearchTemplate(client, new CustomEntityMapper());
}
public static class CustomEntityMapper implements EntityMapper {
private final ObjectMapper objectMapper;
public CustomEntityMapper() {
objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
objectMapper.registerModule(new CustomGeoModule());
objectMapper.registerModule(new JavaTimeModule());
}
#Override
public String mapToString(Object object) throws IOException {
return objectMapper.writeValueAsString(object);
}
#Override
public <T> T mapToObject(String source, Class<T> clazz) throws IOException {
return objectMapper.readValue(source, clazz);
}
}
}
I was experiencing the similar issue: 'Z' in the date value is treated as a character, so the date parse failed. My solution is using custom pattern to make sure the conversion correct:
#Field(type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ss'Z'")
private LocalDateTime dateField;
And if we don't want to repeat the pattern on different fields again and again, we can try to centralize to the conversion logic. Spring Data Elastic Search provide the custom conversion feature, check here for the example.
Basically we can write a converter from String to LocalDateTime, and put the date pattern over there.

Unable to convert String to Date by requestBody in spring

I have the below Code :
DTO :
Class MyDTO {
import java.util.Date;
private Date dateOfBirth;
public Date getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(Date dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
}
Controller
public void saveDOB(#RequestBody MyDTO myDTO, HttpServletRequest httprequest, HttpServletResponse httpResponse) {
System.out.println("Inside Controller");
System.out.println(myDTO.getDateOfBirth());
}
JSON Request :
{
"dateOfBirth":"2014-09-04",
}
If I send the request as yyyy-mm-dd automatic conversion to date object happens.
output in controller:-
dateOfBirth= Thu Sep 04 05:30:00 IST 2014
But when I send DateofBirth in dd-mm-yyyy format It does not convert String to Date automatically.So how i can i handle this case.
JSON Request :
{
"dateOfBirth":"04-09-2014",
}
Output: No Output in console does not even reaches controller.
I have tried with #DateTimeFormat but its not working.
I am using Spring 4.02 Please suggest is there any annotation we can use.
#DateTimeFormat is for form backing (command) objects. Your JSON is processed (by default) by Jackson's ObjectMapper in Spring's MappingJackson2HttpMessageConverter (assuming the latest version of Jackson). This ObjectMapper has a number of default date formats it can handle. It seems yyyy-mm-dd is one of them, but dd-mm-yyyy is not.
You'll need to register your own date format with a ObjectMapper and register that ObjectMapper with the MappingJackson2HttpMessageConverter. Here are various ways to do that :
Configuring ObjectMapper in Spring
Spring, Jackson and Customization (e.g. CustomDeserializer)
Alternatively, you can use a JsonDeserializer on either your whole class or one of its fields (the date). Examples in the link below
Right way to write JSON deserializer in Spring or extend it
How to deserialize JS date using Jackson?
List itemCreate a class to extend JsonDeserializer
public class CustomJsonDateDeserializer extends JsonDeserializer<Date> {
#Override
public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
String date = jsonParser.getText();
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
Use #JsonDeserialize(using = CustomJsonDateDeserializer.class) annotation on setter methods.
Thanks #Varun Achar answer, url

Categories

Resources