Background
{
"markdownPercentage": 20,
"currency": "SEK",
"startDate": "2019-07-25"
}
public class Markdown {
@JsonProperty("markdownPercentage")
@NotNull
private Integer markdownPercentage = 0;
@JsonProperty("currency")
@NotNull
private String currency = "";
@JsonFormat(
shape = Shape.STRING,
pattern = "yyyy-MM-dd"
)
@JsonProperty("startDate")
@NotNull
private ZonedDateTime startDate;
// Constructors, Getters, Setters etc.
}
Problem
When the application tries to deserialize the message to the object it throws the following exception
com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.ZonedDateTime` from String "2019-07-25": Failed to deserialize java.time.ZonedDateTime: (java.time.DateTimeException) Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2019-07-25 of type java.time.format.Parsed
at [Source: (String)"{"styleOption":"so2_GreyMelange_1563966403695_1361997740","markdowns":[{"markdownPercentage":20,"currency":"SEK","startDate":"2019-07-25"},{"markdownPercentage":20,"currency":"NOK","startDate":"2019-07-25"},{"markdownPercentage":20,"currency":"CHF","startDate":"2019-07-25"}]}"; line: 1, column: 126] (through reference chain: com.bestseller.generated.interfacecontracts.kafkamessages.pojos.markdownScheduled.MarkdownScheduled["markdowns"]->java.util.ArrayList[0]->com.bestseller.generated.interfacecontracts.kafkamessages.pojos.markdownScheduled.Markdown["startDate"])
at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1549)
at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:911)
at com.fasterxml.jackson.datatype.jsr310.deser.JSR310DeserializerBase._handleDateTimeException(JSR310DeserializerBase.java:80)
at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:212)
at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:50)
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.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:286)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:245)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:27)
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:3004)
at com.bestseller.mps.functional.TestingConfiguration.test(TestingConfiguration.java:42)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.time.DateTimeException: Unable to obtain ZonedDateTime from TemporalAccessor: {},ISO resolved to 2019-07-25 of type java.time.format.Parsed
at java.base/java.time.ZonedDateTime.from(ZonedDateTime.java:566)
at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:207)
... 35 more
Caused by: java.time.DateTimeException: Unable to obtain ZoneId from TemporalAccessor: {},ISO resolved to 2019-07-25 of type java.time.format.Parsed
at java.base/java.time.ZoneId.from(ZoneId.java:463)
at java.base/java.time.ZonedDateTime.from(ZonedDateTime.java:554)
... 36 more
Current Code
I have the following objectMapper defined
/**
* Date mapper.
*
* @return the {@link ObjectMapper}
*/
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd"));
return mapper;
}
Question
I understand that the resulting ZonedDateTime in the POJO needs a 'time' element which is not present in the source message. I have control only over the objectMapper. Is there any possible configuration that can make this work ?
Note
I am fine if the time element in the deserialised POJO is "assumed" to be startOfDay i.e. "00.00.00.000Z"
We can format a date using the setDateFormat() of ObjectMapper class. This method can be used for configuring the default DateFormat when serializing time values as Strings and deserializing from JSON Strings.
How to deserialize Date from JSON using Jackson. In order to correct deserialize a Date field, you need to do two things: 1) Create a custom deserializer by extending StdDeserializer<T> class and override its deserialize(JsonParser jsonparser, DeserializationContext context) method.
This is how i'm deserializing the date: SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); getObjectMapper(). getDeserializationConfig(). setDateFormat(dateFormat);
The simple readValue API of the ObjectMapper is a good entry point. We can use it to parse or deserialize JSON content into a Java object. Also, on the writing side, we can use the writeValue API to serialize any Java object as JSON output.
unfortunately, you can not deserialize String Object to ZonedDateTime format by default. But you can overcome this problem in two ways. To change ZonedDateTime type to LocalDate type in your POJO class and passing value as a String 03-06-2012 But If you have to store the date and time with timezone then you have to do the following way to overcome
Once the deserializer is registered we must instruct ObjectMapper how we want to proceed if it hits a date. In Jackson 1.7, modules were introduced to extend jackson functionality so we can create a SimpleModule and register our CustomDateDeseralizer class. If you are using spring boot, it configures an ObjectMapper automatically for you.
If you try to use the Person class shown before to deserialize the corresponding JSON contents, you'll get an error. This is because we don't have an empty constructor that Jackson can use during its deserialization logic (using reflection).
You need to convert your String variable startDate to ZonedDateTimne then it will be converted and saved to your DB or whatever. As in coming json startDate is in string format and you have said that you can't change the POJO then, you need to convert it before assigning it to private ZonedDateTime startDate;
I have control only over the
ObjectMapper
. Is there any possible configuration that can make this work?
As long as you are happy with default values for the time and for the timezone, you could work around it with a custom deserializer:
public class ZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> {
@Override
public ZonedDateTime deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext)
throws IOException {
LocalDate localDate = LocalDate.parse(
jsonParser.getText(),
DateTimeFormatter.ISO_LOCAL_DATE);
return localDate.atStartOfDay(ZoneOffset.UTC);
}
}
Then add it to a module and register the module to your ObjectMapper
instance:
SimpleModule module = new SimpleModule();
module.addDeserializer(ZonedDateTime.class, new ZonedDateTimeDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
If adding the deserializer to a module doesn't suit you (in the sense this configuration will be applied to other ZonedDateTime
instances), then you could rely on mix-ins to define which fields the deserializer will be applied to. First define a mix-in interface, as shown below:
public interface MarkdownMixIn {
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
ZonedDateTime getDate();
}
And then bind the mix-in interface to the desired class:
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Markdown.class, MarkdownMixIn.class);
Problem: I would like to parse dates from json to java LocalDateTime/ZonedDateTime objects. ZonedDateTimeSerializer exists but the ZonedDateTimeDeserializer doesn't exist. Hence why I created a custom ZonedDateTimeDeserializer.
public static final String ZONED_DATE_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSz";
@Getter
@Setter
@JsonSerialize(using = ZonedDateTimeSerializer.class)
@JsonDeserialize(using = ZonedDateTimeDeserializer.class) // Doesn't exist, So I created a custom ZonedDateDeserializer utility class.
@JsonFormat(pattern = ZONED_DATE_TIME_FORMAT)
@JsonProperty("lastUpdated")
private ZonedDateTime lastUpdated;
Solution: I ended up with simpler & fewer lines of code.
The Utility Class for deserialising the ZonedDateTime:
/**
* Custom {@link ZonedDateTime} deserializer.
*
* @param jsonParser for extracting the date in {@link String} format.
* @param deserializationContext for the process of deserialization a single root-level value.
* @return {@link ZonedDateTime} object of the date.
* @throws IOException throws I/O exceptions.
*/
public class ZonedDateTimeDeserializer extends JsonDeserializer<ZonedDateTime> {
@Override
public ZonedDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
return ZonedDateTime.parse(jsonParser.getText(), DateTimeFormatter.ofPattern(ZONED_DATE_TIME_FORMAT));
}
}
What if you're using LocalDateTime instead. In that case it's even easier, both the deserializer & the serializer classes are already provided to us. No need for custom utility classes as defined above:
@Getter
@Setter
@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonFormat(pattern = ZONED_DATE_TIME_FORMAT) //Specify the format you want: "yyyy-MM-dd'T'HH:mm:ss.SSS"
@JsonProperty("created")
private LocalDateTime created;
Other links that also helped with the research: ideas that led to this solution
Keywords: json format localDateTime zonedDateTime
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With