custom deserializer isn't called despite the #JsonDeserializer annotation - java

I am using Jackson with Jersey in Java.
Using the web API I am trying to send a POJO via a JSON file that also consists of a Joda DateTime object.
Because that is not a native Object, I built a custom serializer and deserializer for it. However, because the deserialiser does not seem to work, I send a POST request, but it isn't received on the server. Of course, when I move the date field from the JSON, it works perfectly.
The JSON being sent:
{"loc": [-0.1300836,51.5124623],
"visibility":"Public",
"date": "06 January 2014 09:51"}
The POST method receiving it:
#POST
#Timed
public String createTadu(#Valid Tadu tadu) throws JsonParseException, JsonMappingException, IOException {
Tadu createdTadu = new Tadu(tadu);
taduCollection.insert(createdTadu);
return String.format("{\"status\":\"success\", \"id\":\"%s\"}", tadu.getId());
}
The POJO DateTime declaration:
private DateTime date;
#JsonSerialize(using = CustomDateSerializer.class)
public DateTime getDate() {
return date;
}
#JsonDeserialize(using = CustomDateDeserializer.class)
public void setDate(DateTime date) {
this.date = date;
}
and the custom serializer/deserializer:
public class CustomDateSerializer extends JsonSerializer<DateTime> {
private static DateTimeFormatter formatter = DateTimeFormat
.forPattern("dd MMMM yyyy HH:mm"); // 31 December 2013 16:22
#Override
public void serialize(DateTime value, JsonGenerator gen,
SerializerProvider arg2) throws IOException,
JsonProcessingException {
gen.writeString(formatter.print(value));
}
}
public class CustomDateDeserializer extends JsonDeserializer<DateTime>
{
#Override
public DateTime deserialize(JsonParser jsonparser,
DeserializationContext deserializationcontext) throws IOException, JsonProcessingException {
DateTimeFormatter formatter = DateTimeFormat.forPattern("dd MMMM yyyy HH:mm");
String date = jsonparser.getText();
return formatter.parseDateTime(date);
}
}
It is clear to me that the POST method (createTadu) is not being called because a String called "date" is sent via the JSON, but it is not a valid Tadu object because that is not a DateTime called "date". I was hoping that the custom deserializer annotation would fix that.
Thanks!

I couldn't understand why this method was not working. I ended up downloading the jackson-datatype-joda module.
The only issue with this module is that it yet does not allow custom formatters of date time and only operates with ISO 8601.
Another issue is that in the database the date time is converted to an epoch UTC time, with an added 4 zeros in the end (why the zeros..). So when fetching it back to the client a conversion from epoch to a presentable date needed to be done.

Related

Accept Arabic date as query parameter in Spring Boot

I want to accept Arabic date in the query parameter
ex:
٢٠٢١-٠٧-٢٠ the English version of this date is 2021-07-20
I have found this soltion
DecimalStyle defaultDecimalStyle
= DateTimeFormatter.ISO_LOCAL_DATE.getDecimalStyle();
DateTimeFormatter arabicDateFormatter = DateTimeFormatter.ISO_LOCAL_DATE
.withDecimalStyle(defaultDecimalStyle.withZeroDigit('\u0660'));
String encodedArabicDateStr = "%D9%A2%D9%A0%D9%A1%D9%A9-%D9%A0%D9%A4-%D9%A1%D9%A5";
String arabicDateStr
= URLDecoder.decode(encodedArabicDateStr, StandardCharsets.UTF_8);
LocalDate date = LocalDate.parse(arabicDateStr, arabicDateFormatter);
System.out.println("Parsed date: " + date);
And it works as expected
now, in my Spring Boot application, I handle the LocalDate with #DateTimeFormat with pattern
#DateTimeFormat(pattern = "yyyy-MM-dd") val fromDate: LocalDate
How I can tell the Spring Boot to use a custom method to parse the date?
The original problem is when someone send Arabic date the application it will crash with the below error:
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'dateRange' on field 'fromDate': rejected value [٢٠٢١-٠٧-٢٠];
codes [typeMismatch.dateRange.fromDate,typeMismatch.fromDate,typeMismatch.java.time.LocalDate,typeMismatch];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [dateRange.fromDate,fromDate];
arguments [];
default message [fromDate]];
default message [Failed to convert value of type 'java.lang.String[]' to required type 'java.time.LocalDate';
nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [#org.springframework.format.annotation.DateTimeFormat java.time.LocalDate] for value '٢٠٢١-٠٧-٢٠'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [٢٠٢١-٠٧-٢٠]]
I have tried:
update request values by filter but it does not work.
FormattingConversionService like this sample.
You can create your own custom deserializer for LocalDate which will contain the format field.
Creating of custom deserializer for LocalDate
public class CustomDateDeserializer extends JsonDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
DecimalStyle defaultDecimalStyle = DateTimeFormatter.ISO_LOCAL_DATE.getDecimalStyle();
DateTimeFormatter arabicDateFormatter = DateTimeFormatter.ISO_LOCAL_DATE.withDecimalStyle(defaultDecimalStyle.withZeroDigit('\u0660'));
String arabicDateStr = URLDecoder.decode(jsonParser.getText(), StandardCharsets.UTF_8);
return LocalDate.parse(arabicDateStr, arabicDateFormatter);
}
}
Using the custom deserializer for LocalDate
#JsonSerialize(using = CustomDateDeserializer.class)
private LocalDate fromDate;
Since you need to parse the date from query parameter, you need to implement the logic as a Converter.
#Component
public class ArabicDateConverter implements Converter<String, LocalDate> {
private final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE
.withDecimalStyle(DateTimeFormatter.ISO_LOCAL_DATE.getDecimalStyle().withZeroDigit('\u0660'));
#Override
public LocalDate convert(String source) {
return LocalDate.parse(source, formatter);
}
}
Note #Component, it's used to register the converter.
Keep in mind that this converts only arabic dates, it will fail for other formats. If you need to parse others, you may need to keep a list of possible formats to iterate over until something matches.

custom deserializing a date with format

["last_modified"])] with root cause
java.time.format.DateTimeParseException: Text '2018-06-06T13:19:53+00:00' could not be parsed, unparsed text found at index 19
The inbound format is 2018-06-06T13:19:53+00:00
It's a weird format.
I have tried the following:
public class XYZ {
#DateTimeFormat(pattern = "yyyy-MM-ddTHH:mm:ss+00:00", iso = ISO.DATE_TIME)
private LocalDateTime lastModified;
}
There is nothing stopping you from creating your own deserializer. A very naive example could be the following:
public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
private static final String PATTERN = "yyyy-MM-dd'T'HH:mm:ss+00:00";
private final DateTimeFormatter formatter;
public LocalDateTimeDeserializer() {
this.formatter = DateTimeFormatter.ofPattern(PATTERN);
}
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return LocalDateTime.parse(p.getText(), formatter);
}
}
The only thing you need to notice is that you'll need to escape the 'T' by adding single quote around it.
With the deserializer in place you can simply annotate the field like so:
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime dateTime;
The inbound format is 2018-06-06T13:19:53+00:00
It's a weird format.
That's the ISO 8601 format, which is endorsed by the RFC 3339 and by the xkcd 1179:
The following should work as expected when receiving the values as query parameters:
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDate dateTime;
As 2018-06-06T13:19:53+00:00 represents a date and time with an offset from UTC, you'd better use OffsetDateTime rather than LocalDateTime:
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private OffsetDateTime dateTime;
Just ensure that + is encoded as %2B.
With Jackson, you could add the jackson-datatype-jsr310 dependency to your application. This module will provide you with serializers and deserializers for java.time types.
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
And then register the JavaTimeModule module in your ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Jackson will handle the serialization and deserialization for you.
If you are, for some reason, not interested in the offset from UTC and want to keep using LocalDateTime, you could extend the LocalDateTimeDeserializer provided by Jackson and use a custom DateTimeFormatter:
public class CustomLocalDateTimeDeserializer extends LocalDateTimeDeserializer {
public CustomLocalDateTimeDeserializer() {
super(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}
Then annotate the LocalDateTime field as shown below:
#JsonDeserialize(using = CustomLocalDateTimeDeserializer.class)
private LocalDateTime dateTime;
The inbound format is 2018-06-06T13:19:53+00:00
If you're able to set the Date Format on your entire ObjectMapper, you could do the following:
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
objectMapper.setDateFormat(df);
This is part of the examples from the SimpleDateFormat Javadocs

Jackson , java.time , ISO 8601 , serialize without milliseconds

I'm using Jackson 2.8 and need to communicate with an API that doesn't allow milliseconds within ISO 8601 timestamps.
The expected format is this: "2016-12-24T00:00:00Z"
I'm using Jackson's JavaTimeModule with WRITE_DATES_AS_TIMESTAMPS set to false.
But this will print milliseconds.
So I tried to use objectMapper.setDateFormat which didn't change anything.
My current workaround is this:
ObjectMapper om = new ObjectMapper();
DateTimeFormatter dtf = new DateTimeFormatterBuilder()
.appendInstant(0)
.toFormatter();
JavaTimeModule jtm = new JavaTimeModule();
jtm.addSerializer(Instant.class, new JsonSerializer<Instant>() {
#Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
gen.writeString(dtf.format(value));
}
});
om.registerModule(jtm);
I'm overriding the default serializer for Instant.class which works.
Is there any nice way using some configuration parameter to solve this?
Update:
Just add a #JsonFormat annotation with the date format above the Instant property. It's very easy.
In the case you have an ObjectMapper with the JavaTimeModule like next:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
If you have a class with an Instant property, you should add the #JsonFormat annotation and put the date pattern which hasn't milliseconds. It would be like next:
public static class TestDate {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss", timezone = "UTC")
Instant instant;
//getters & setters
}
So if you serialize an object to Json it works perfectly:
String json = mapper.writeValueAsString(testDate);
System.out.println(json);
Output
{"instant":"2016-11-10 06:03:06"}
Old Answer. I don't know why but It doesn't work properly:
You could use the Jackson2ObjectMapperBuilder to build it.
You just need to add the dateFormat you want. It would be something like next:
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
ObjectMapper mapper = Jackson2ObjectMapperBuilder
.json()
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.modules(new JavaTimeModule())
.dateFormat(dateFormat)
.build();
Here is an alternative which is something you can set globally, but will need you to use ZonedDateTime with instant formatter as we can't set the format for the Instant Serializer provided with Java Time Module.
You wont see any side effects of using zoned date time for instant as jackson serializes the zone id separately and is disabled by default. So technically, this is similar to applying the formatter to Instant.
When used this way, the ZonedDateTime serializer delegates the serialization to InstantBaseSerializer and uses the specified custom format.
#RunWith(JUnit4.class)
public class InstantNoMillisTest {
private ObjectMapper objectMapper;
#Before
public void init() {
JavaTimeModule module = new JavaTimeModule();
ZonedDateTimeSerializer zonedDateTimeSerializer = new ZonedDateTimeSerializer(new DateTimeFormatterBuilder().appendInstant(0).toFormatter());
module.addSerializer(ZonedDateTime.class, zonedDateTimeSerializer);
module.addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME);
objectMapper = Jackson2ObjectMapperBuilder.json()
.modules(module)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.build();
}
#Test
public void serialize() throws IOException {
ZonedDateTime zonedDateTime = ZonedDateTime.now();
String noMillis = objectMapper.writeValueAsString(zonedDateTime);
System.out.print(noMillis);
}
#Test
public void deserialize() throws IOException {
String dateTime = "\"2017-10-26T12:54:59Z\"";
ZonedDateTime noMillis = objectMapper.readValue(dateTime, ZonedDateTime.class);
System.out.print(noMillis);
}
}
Here's some Kotlin code of formatting Instant fields, so they will not contain milliseconds, you can use custom date formatters
ObjectMapper().apply {
val javaTimeModule = JavaTimeModule()
javaTimeModule.addSerializer(Instant::class.java, Iso8601WithoutMillisInstantSerializer())
registerModule(javaTimeModule)
disable(WRITE_DATES_AS_TIMESTAMPS)
}
private class Iso8601WithoutMillisInstantSerializer
: InstantSerializer(InstantSerializer.INSTANCE, false, DateTimeFormatterBuilder().appendInstant(0).toFormatter())

Jackson 2.2.2 ObjectMapper date format doesn't work for Joda DateTime

In my project I have following ObjectMapper class:
public class ObjectMapperImpl extends ObjectMapper {
public ObjectMapperImpl() {
super();
configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
registerModule(new JodaModule());
setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
}
so as you can see I am using JodaModule to serialize/deserialize org.joda.time.DateTime
But my date format doesn't applies during serialization process and it produces date like this: 2013-06-15T09:50:08.541Z
I took a look on JodaModule and I see that it registers following
addSerializer(DateTime.class, new DateTimeSerializer());
public final class DateTimeSerializer
extends JodaSerializerBase<DateTime>
{
public DateTimeSerializer() { super(DateTime.class); }
#Override
public void serialize(DateTime value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonGenerationException
{
if (provider.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) {
jgen.writeNumber(value.getMillis());
} else {
jgen.writeString(value.toString());
}
}
#Override
public JsonNode getSchema(SerializerProvider provider, java.lang.reflect.Type typeHint)
{
return createSchemaNode(provider.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
? "number" : "string", true);
}
}
so according to this code I can say that it never uses date format defined in configuration it simply uses toString() method to convert it to string
#ToString
public String toString() {
return ISODateTimeFormat.dateTime().print(this);
}
and date format is hard coded and it's ISO.
So as I guess it's impossible to setup my own dateformat in ObjectMapper configuration for joda's DateTime.
Does it means that only solutions is to get rid of JodaModule and create my custom serializer/deserializer to support date format?
Looking for your oppinions.
Thank you.

jackson JodaTime DateTime is not serialized

I am trying to start working with jackson for serializing objects having Joda DateTime members.
I created the following serializer:
public class DateTimeSerializer extends JsonSerializer<DateTime> {
private static DateTimeFormatter formatter =
DateTimeFormat.forPattern("dd-MM-yyyy HH:mm:ss, SSS [z]");
#Override
public void serialize(DateTime value, JsonGenerator gen,
SerializerProvider arg2) throws IOException,
JsonProcessingException {
gen.writeString(formatter.print(value));
}
#Override
public Class<DateTime> handledType() {
return DateTime.class;
}
}
and I am trying to serialize my class having DateTime members as follows:
Version version = new Version(1, 0, 0, "SNAPSHOT");
SimpleModule module = new SimpleModule("ZORRO", version);
module = module.addSerializer(new DateTimeSerializer());
// and so on...
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
mapper.writeValue(new File("C:\\tmp\\window1.json"), window1);
I am getting the following exception (I fail to understand what I was doing wrong):
java.lang.TypeNotPresentException: Type org.joda.convert.ToString not present
at com.ibm.oti.reflect.AnnotationHelper.getAnnotation(AnnotationHelper.java:38)
at com.ibm.oti.reflect.AnnotationHelper.getDeclaredAnnotations(AnnotationHelper.java:50)
at com.ibm.oti.reflect.Method.getDeclaredAnnotations(Method.java:31)
at java.lang.reflect.Method.getDeclaredAnnotations(Method.java:687)
at org.codehaus.jackson.map.introspect.AnnotatedClass._addMixUnders(AnnotatedClass.java:973)
at org.codehaus.jackson.map.introspect.AnnotatedClass._addMemberMethods(AnnotatedClass.java:637)
at org.codehaus.jackson.map.introspect.AnnotatedClass.resolveMemberMethods(AnnotatedClass.java:413)
at org.codehaus.jackson.map.introspect.BasicClassIntrospector.classWithCreators(BasicClassIntrospector.java:185)
at org.codehaus.jackson.map.introspect.BasicClassIntrospector.collectProperties(BasicClassIntrospector.java:157)
at org.codehaus.jackson.map.introspect.BasicClassIntrospector.forSerialization(BasicClassIntrospector.java:96)
at org.codehaus.jackson.map.introspect.BasicClassIntrospector.forSerialization(BasicClassIntrospector.java:16)
at org.codehaus.jackson.map.SerializationConfig.introspect(SerializationConfig.java:973)
at org.codehaus.jackson.map.ser.BeanSerializerFactory.createSerializer(BeanSerializerFactory.java:251)
at org.codehaus.jackson.map.ser.StdSerializerProvider._createUntypedSerializer(StdSerializerProvider.java:782)
at org.codehaus.jackson.map.ser.StdSerializerProvider._createAndCacheUntypedSerializer(StdSerializerProvider.java:758)
at org.codehaus.jackson.map.ser.StdSerializerProvider.findValueSerializer(StdSerializerProvider.java:380)
at org.codehaus.jackson.map.ser.std.BeanSerializerBase.resolve(BeanSerializerBase.java:307)
at org.codehaus.jackson.map.ser.impl.SerializerCache.addAndResolveNonTypedSerializer(SerializerCache.java:159)
at org.codehaus.jackson.map.ser.StdSerializerProvider._createAndCacheUntypedSerializer(StdSerializerProvider.java:744)
at org.codehaus.jackson.map.ser.StdSerializerProvider.findValueSerializer(StdSerializerProvider.java:344)
at org.codehaus.jackson.map.ser.StdSerializerProvider.findTypedValueSerializer(StdSerializerProvider.java:420)
at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:601)
at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:256)
at org.codehaus.jackson.map.ObjectMapper._configAndWriteValue(ObjectMapper.java:2575)
at org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:2048)
Do you have the optional joda-convert jar on your classpath?
BTW, is this a runtime or compile-time error?
it's not Joda DateTime that's not serializable, it's the DateTimeFormatter.
looking at the class signature for those 2:
public final class DateTime
extends BaseDateTime
implements ReadableDateTime, Serializable {
public class DateTimeFormat {
now it's very clear that you should not try to serialize the formatter. but DateTime itself is perfectly fine since it implements java.io.Serializable.

Categories