Unable to serialize/deserialize ZonedDateTime correctly using jackson - java

I'd like to serialize/deserialize ZonedDateTime in my spring boot app, so I need to customise the ObjectMapper. But when I deserialize it back, I can not get the ZonedDateTime correctly.
Here's my sample code:
ObjectMapper mapper = new ObjectMapper()
.enable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.findAndRegisterModules();
ZonedDateTime dateTime = ZonedDateTime.now();
String json = mapper.writeValueAsString(dateTime);
LOGGER.info("ZonedDateTime json: " + json);
ZonedDateTime dateTime2 = mapper.readValue(json, ZonedDateTime.class);
assertEquals(dateTime, dateTime2);
This test fails with following:
org.opentest4j.AssertionFailedError:
Expected :2022-12-12T18:00:48.711+08:00[Asia/Shanghai]
Actual :2022-12-12T10:00:48.711Z[UTC]

Sorry folks, it seems my fault. I should specify the zoneId when creating ZonedDateTime.
The following code pass:
ZonedDateTime dateTime = ZonedDateTime.now(ZoneId.of("UTC"));
String json = mapper.writeValueAsString(dateTime);
LOGGER.info("ZonedDateTime json: " + json);
ZonedDateTime dateTime2 = mapper.readValue(json, ZonedDateTime.class);
assertEquals(dateTime, dateTime2);

You don't need to customize ObjectMapper. Try to add this annotation above your property:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
ZonedDateTime myDate
For more details look at this question and the accepted answer: Spring Data JPA - ZonedDateTime format for json serialization

Related

Jackson unable to parse ISO8601

We get a HttpResponse with a date as one json attribute, the date is formatted in ISO8601 (e.g. 2020-03-13T00:00:35.570+0000) but Jackson throws following Exception:
java.time.format.DateTimeParseException: Text '2020-03-13T00:00:35.570+0000' could not be parsed at index 23
I`ve written the following Test (spock) which fails reproduceable.
I need to know how to parse the date.
Thanks for your help!
class TestJackson extends Specification{
def 'test date format'(){
given:
def jsonString = """{"myDate":"2020-03-13T00:00:35.570+0000"}"""
and:
def objectMapper = new ObjectMapper()
.registerModule(new JavaTimeModule())
.enable(SerializationFeature.INDENT_OUTPUT)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
when:
def resp = objectMapper.readValue(jsonString, Response)
then:
resp.myDate != null
}
#Data
#NoArgsConstructor
#AllArgsConstructor
static class Response {
ZonedDateTime myDate
}
}
The Test uses following Dependencies:
com.fasterxml.jackson.core:jackson-databind:2.10.3
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.10.3
Jackson is not the issue here; you'd get the same exception if you called ZonedDateTime.parse("2020-03-13T00:00:35.570+0000"). According to the API, ZonedDateTime uses DateTimeFormatter.ISO_ZONED_DATE_TIME to parse. ISO_ZONED_DATE_TIME is
a date-time with offset and zone, such as
'2011-12-03T10:15:30+01:00[Europe/Paris]'
The value you were trying to parse has an offset but no zone so you need to convert it to OffsetDateTime, which uses DateTimeFormatter.ISO_OFFSET_DATE_TIME to parse. DateTimeFormatter.ISO_OFFSET_DATE_TIME
...parses a date-time with an offset, such as '2011-12-03T10:15:30+01:00'.

How to use jackson to parse RFC3339 timestamp with variable number of second fractions

I'm trying to parse timestamps in the RFC3339 format in a JSON-string using Jackson. How do I allow for the variable amount of decimal places after the seconds?
For the JSON file
{
"timestamp": "2019-07-02T13:00:34.836+02:00"
}
I've deserialized it with the class
public abstract class Attribute {
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
public Date timestamp;
}
and an ObjectMapper with the JavaTimeModule:
ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule());
mapper.readValue(jsonFile, Attribute.class);
This works fine. However, it should also work for "timestamp": "2019-07-02T13:00:34+02:00" and "timestamp": "2019-07-02T13:00:34.090909090+02:00". I found this answer showing how to parse such strings with a DateTimeFormatter, but as far as I can tell, #JsonFormat only takes a SimpleDateFormat string, which do not have support for variable amounts of second decimals.
Removing the pattern-property all together, so the annotation becomes
#JsonFormat(shape = JsonFormat.Shape.STRING)
allows me to parse the incoming dates, but also accepts non-RFC3339 timestamps like 1990-01-01T12:53:01-0110 (missing a colon in the timezone).
Once the JavaTimeModule is registered in your ObjectMapper, simply use OffsetDateTime instead of Date. There's no need for #JsonFormat.
See the example below:
#Data
public class Foo {
private OffsetDateTime timestamp;
}
String json =
"{\n" +
" \"timestamp\": \"2019-07-02T13:00:34.090909090+02:00\"\n" +
"}\n";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
Foo foo = mapper.readValue(json, Foo.class);

SpringBoot2 #DateTimeFormat annotation with LocalDateTime doesn't work as i expected

i want to convert LocalDateTime to YYYY-MM-DD HH:mm:ss format with #DateTimeFormat.
following is my source code.
Entity Class
#Entity // JPA Annotation
#Setter // Lombok Annotations
#Getter
public class CustomModel {
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime addDate;
#DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime lastModifyDate;
}
Controller Class
...
CustomModel customModel = new CustomModel();
customModel.setAddDate(LocalDateTime.now());
customModel.setLastModifyDate(LocalDateTime.now());
...
and following is result.
In above image, i expected '2019-08-11 03:08:56'.
But result format is '2019-08-11 03:08:56.814944'.
what is wrong??
You didn't put the thymeleaf template, but the #DateTimeFormat annotation will only be taken into account if you use double bracket rather than simple bracket, i.e. ${{model.addDate}} rather than ${model.addDate}.
You can use DateTimeFormatter class. But to convert, you need String representation of your date. Then parse it.
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
customModel.setAddDate(LocalDateTime.parse(LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)), formatter);
customModel.setLastModifyDate(LocalDateTime.parse(LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME)), formatter);

Jackson - Can't deserialize datetime with timezone offset 'unparsed text found at index 23'

My datetime has to come from frontend with timezone offset: 2017-07-04T06:00:00.000+01:00
I cannot deserialize it with Jackson.
The error is:
Text '2017-07-04T06:00:00.000+01:00' could not be parsed, unparsed
text found at index 23;
I was trying to Google the solution by all are about DateTime with Z at the end.
#NotNull
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS aZ")
private LocalDateTime time;
Is there any solution for that?
The pattern a is used to parse AM/PM, which is not in the input String, that's why you get a parse error.
The input format matches an OffsetDateTime, which can be parsed with the respective built-in formatter DateTimeFormatter.ISO_OFFSET_DATE_TIME, so you can use this formatter in a deserializer object and register it in the module. You must also remove the JsonFormat annotation from the field.
ObjectMapper om = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
LocalDateTimeDeserializer deserializer = new LocalDateTimeDeserializer(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
module.addDeserializer(LocalDateTime.class, deserializer);
om.registerModule(module);
This will parse the input and convert it to a LocalDateTime. In the test I've made, the value of LocalDateTime was set to 2017-07-04T06:00.
To control the output, you can either do:
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Which will output the LocalDateTime as 2017-07-04T06:00:00, or you can use a custom formatter:
LocalDateTimeSerializer serializer = new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS a"));
module.addSerializer(LocalDateTime.class, serializer);
The serializer above will output the field as 2017-07-04T06:00:00.000 AM. Please note that the Z pattern will not work because a LocalDateTime has no timezone information and it can't resolve its offset - because when you deserialized to a LocalDateTime, the offset information in the input (+01:00) was lost.
Another alternative (without the need to configure the object mapper) is to use the correct pattern in the annotation:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSS[xxx]")
private LocalDateTime time;
Note that I used the pattern [xxx] because the offset (+01:00) can be optional: when deserializing, this information is lost becase a LocalDateTime has no information about timezones and offsets, so when serializing this field won't be found - making the field optional (using [] delimiters) make it work for both deserialization and serialization.
This will deserialize the input 2017-07-04T06:00:00.000+01:00 and serialize to 2017-07-04T06:00:00.000 (note that the optional offset is not used in serialization, as the LocalDateTime has no such information).
If you want different formats for deserialization and serialization, you can also create custom classes and anotate them in the field:
public class CustomDeserializer extends LocalDateTimeDeserializer {
public CustomDeserializer() {
super(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
}
public class CustomSerializer extends LocalDateTimeSerializer {
public CustomSerializer() {
super(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS a"));
}
}
// in this case, don't use #JsonFormat
#JsonSerialize(using = CustomSerializer.class)
#JsonDeserialize(using = CustomDeserializer.class)
private LocalDateTime time;
This will use the format 2017-07-04T06:00:00.000+01:00 for deserialize and the format 2017-07-04T06:00:00.000 AM to serialize.

Jackson date format with #JsonFormat?

I want to serialize certain Calendar fields of a POJO with a specific format.
with no annotations, fields like this:
private Calendar timestamp1;
private Calendar timestamp2;
produce JSON like this:
{ ..., timestamp1: 1402402106000, timestamp2: 1402488595000, ... }
I would to add a field formatted as a string as it actually represents a Day as a 24-hour unit, not a specific instant of time. But when I add a new field with an annotation:
#JsonFormat(pattern = "yyyy-MM-dd")
private Calendar oneDay;
I was hoping to get JSON like this:
{ ..., timestamp1: 1402402106000, timestamp2: 1402488595000, oneDay: "2014-06-12", ... }
Instead, I got a the following exception:
com.fasterxml.jackson.databind.JsonMappingException:
Cannot format given Object as a Date
(through reference chain: java.util.HashMap["data"]->java.util.ArrayList[0]-myPojo["oneDay"])
What am I doing wrong?
I'm using Jackson 2.2.0
Here's what I've used: #JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
That works for me.

Categories

Resources