DynamoDBMapper for java.time.LocalDateTime - java

I'm utilizing java.time.LocalDateTime in my java application. I'm also trying to use DynamoDBMapper and via the annotation save the LocalDateTime variable. Unfortunately I get the following error:
DynamoDBMappingException: Unsupported type: class java.time.LocalDateTime
Is there a way to have this mapping without using DynamoDBMarshalling?

No AWS DynamoDB Java SDK can't map java.time.LocalDateTime natively without using any annotation.
To do this mapping, you have to use DynamoDBTypeConverted annotation introduced in the version 1.11.20 of the AWS Java SDK. Since this version, the annotation DynamoDBMarshalling is deprecated.
You can do that like this:
class MyClass {
...
#DynamoDBTypeConverted( converter = LocalDateTimeConverter.class )
public LocalDateTime getStartTime() {
return startTime;
}
...
static public class LocalDateTimeConverter implements DynamoDBTypeConverter<String, LocalDateTime> {
#Override
public String convert( final LocalDateTime time ) {
return time.toString();
}
#Override
public LocalDateTime unconvert( final String stringValue ) {
return LocalDateTime.parse(stringValue);
}
}
}
With this code, the stored dates are saved as string in the ISO-8601 format like that: 2016-10-20T16:26:47.299.

Despite what I said I found it simple enough to use DynamoDBMarshalling to marshal to and from a string. Here is my code snippet and an AWS reference:
class MyClass {
...
#DynamoDBMarshalling(marshallerClass = LocalDateTimeConverter.class)
public LocalDateTime getStartTime() {
return startTime;
}
...
static public class LocalDateTimeConverter implements DynamoDBMarshaller<LocalDateTime> {
#Override
public String marshall(LocalDateTime time) {
return time.toString();
}
#Override
public LocalDateTime unmarshall(Class<LocalDateTime> dimensionType, String stringValue) {
return LocalDateTime.parse(stringValue);
}
}
}

Related

Unable to combine Annotations

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?

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.

How to force spring org.springframework.format.annotation.DateTimeFormat to custom DateFormatter

I have a scenario where I have different types of dates coming, in that case the existing spring conversion is failing as vales coming are in different formats.
Is there a way I can make Spring use my custom DateFormatter ?
Is there a way I can make Spring use my custom DateFormatter ?
Yes but as your use case is specific I believe it is better to use a custom annotation to make everything explicit.
These interfaces can be used to accomplish this task:
Formatter
AnnotationFormatterFactory
These classes source code can be used as a reference:
Jsr310DateTimeFormatAnnotationFormatterFactory
DateTimeFormatAnnotationFormatterFactory
NumberFormatAnnotationFormatterFactory
You could do something like this:
The UnstableDateFormats annotation
#Retention(RUNTIME)
public #interface UnstableDateFormats {
String[] formatsToTry();
}
The Formatter implementation
public class UnstableDateFormatter implements Formatter<LocalDate> {
private final List<String> formatsToTry;
public UnstableDateFormatter(List<String> formatsToTry) {
this.formatsToTry = formatsToTry;
}
#Override
public LocalDate parse(String text, Locale locale) throws ParseException {
for (String format : formatsToTry) {
try {
return LocalDate.parse(text, DateTimeFormatter.ofPattern(format));
} catch (DateTimeParseException ignore) {
// or log the exception
}
}
throw new IllegalArgumentException("Unable to parse \"" + text
+ "\" as LocalDate using formats = " + String.join(", ", formatsToTry));
}
#Override
public String print(LocalDate object, Locale locale) {
// Implement this method thoroughly
// If you're accepting dates in different formats which one should be used to print the value?
return object.toString();
}
}
The AnnotationFormatterFactory implementation
public class UnstableDateFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<UnstableDateFormats> {
#Override
public Set<Class<?>> getFieldTypes() {
return Collections.singleton(LocalDate.class);
}
#Override
public Printer<?> getPrinter(UnstableDateFormats annotation, Class<?> fieldType) {
return new UnstableDateFormatter(Arrays.asList(annotation.formatsToTry()));
}
#Override
public Parser<?> getParser(UnstableDateFormats annotation, Class<?> fieldType) {
return new UnstableDateFormatter(Arrays.asList(annotation.formatsToTry()));
}
}
Don't forget to register the AnnotationFormatterFactory implementation:
If you are using spring mvc you can do it in the web configuration (see Type Conversion):
public class MvcConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new UnstableDateFormatAnnotationFormatterFactory());
}
}
See also:
Spring Field Formatting
You may also want to consider:
Configuring a Global Date and Time Format

Moshi JsonAdapter.Factory: Same type of fields, but different String format in Json

I've exposed my problem on Moshi GitHub.
Resume: how to deserialize a json into a class which have 2 fields of same type, but different JSon formats, using custom annotation for these formats?
With the hint given by JakeWharton, I've successfully obtained the wanted result.
I share it here, for anyone looking for a solution.
Is there a better way to find the annotation in the create method of the factory? Or even a better way to achieve my goal in this method?
Here the (simplified) code, without the adapter class, which is the easy part.
The example Java class for Json
public class Data
{
#DateFormat("yyyy-MM-dd")
LocalDate date1;
#DateFormat("ddMMyyyy")
LocalDate date2;
}
The DateFormat annotation
#Retention(RetentionPolicy.RUNTIME)
#JsonQualifier
#interface DateFormat
{
String value();
}
The JsonAdapter.Factory based on DateFormat annotation for LocalDate
public class DateFormatFactory implements JsonAdapter.Factory
{
#Override
public #Nullable JsonAdapter<?> create(Type type_p, Set<? extends Annotation> annotations_p, Moshi moshi_p)
{
if (annotations_p.isEmpty() || !type_p.getTypeName().equals(LocalDate.class.getName()))
{
return null;
}
for (Annotation annotation : annotations_p)
{
if (annotation instanceof DateFormat)
{
return new LocalDateAdapter(((DateFormat) annotation).value());
}
}
return null;
}
}
Thank you for your time!

Changing default json for an object in Spring Boot Application

I have a simple Spring Boot Application having a simple MyDateTime model class only having a java.util.Date instance variable with private access, getters/setters and default constructor.
A controller just instantiates this object and returns back.
In the output, I see that default representation of Date object is done as an Integer (maybe millis from Epoch)
Is there any way I can change the default jsonification of Date Object into ISO-String or any other String?
EDIT:
Some Clarification:
I'm very new to Spring and Spring Boot. I'm using the template from a sample application on spring's website. JSONification is done through Jackson. Rest, I don't know much about Spring in general.
You can set the default format Jackson uses when serializing dates in your application.properties file:
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
http://docs.spring.io/spring-boot/docs/1.2.3.RELEASE/reference/htmlsingle/#common-application-properties
You can also specify a specific format to use for a specific date using the #JsonFormat annotation, example below:
Example POJO:
public class Demo {
private Date timestamp1;
private Date timestamp2;
public Date getTimestamp1() {
return timestamp1;
}
public void setTimestamp1(Date timestamp1) {
this.timestamp1 = timestamp1;
}
#JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ")
public Date getTimestamp2() {
return timestamp2;
}
public void setTimestamp2(Date timestamp2) {
this.timestamp2 = timestamp2;
}
}
Example Controller:
#RestController
public class DemoController {
#RequestMapping(value="/demo", method = RequestMethod.GET)
Demo start() {
Demo demo = new Demo();
Date timestamp = new Date();
demo.setTimestamp1(timestamp);
demo.setTimestamp2(timestamp);
return demo;
}
}
https://github.com/FasterXML/jackson-annotations/wiki/Jackson-Annotations
Date.toString() method returns epoch timestamp by default. What you want to do is about Jackson internals. To accomplish that, change your MyDateTime class to something like this:
public class MyDateTime {
private final Date date;
public MyDateTime(Date date) {
this.date = date;
}
public Date date() { //this is not read by Jackson
return date;
}
public String getDate() { //this is read by Jackson
SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
return formatter.format(date);
}
}
When serializing, Jackson look for getter methods, the methods that start with get. If you don't want something to be serialized, remove get from the name. This is the simplest solution, you can check Jackson API for #JsonIgnore annotation for further solutions. If you want to see something other date in the field name in JSON string, look for #JsonProperty.
To print human readable date, you need SimpleDateFormat.
The examples works fine on my setup, I did not do anything further than returning custom class from HelloController.

Categories

Resources