Jackson deserialize ISO8601 formatted date-time into Java8 Instant
Asked Answered
F

6

75

I'm trying to deserialize an ISO8601 formatted date into Java8 java.time.Instant using Jackson. I registered JavaTimeModule with the ObjectMapper, and turned off the WRITE_DATES_AS_TIMESTAMPS setting.

However, if one tries to deserialize 2016-03-28T19:00:00.000+01:00 it will not work, because it seems that JavaTimeModule will only deserialize date-times formatted with UTC timezone offset (e.g. 2016-03-28T18:00:00.000Z). I then tried using @JsonFormat annotation like this:

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone = "UTC")

And like this:

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone = JsonFormat.DEFAULT_TIMEZONE)

However, neither of these work and I get an exception:

com.fasterxml.jackson.databind.JsonMappingException: Unsupported field: YearOfEra (through reference chain: org.example.Article["date"])

Which implies that timezone parameter is ignored and date time formatter doesn't know how to format an Instant without a timezone.

Is there a way to deserialize a ISO8601 string that's not in UTC time zone offset to Java 8 java.time.Instant using Jackson and JavaTimeModule without writing a custom deserializer?

Fernandina answered 27/3, 2016 at 20:55 Comment(6)
strange, I expected this error Failed to parse Date value '2016-03-28T19:00:00.000+01:00' (format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ"): Unparseable date: "2016-03-28T19:00:00.000+01:00" and after trying your code, I got this kind of error in both cases. The format is not matching... did you tried it without any annotations?Anthologize
or with this pattern: yyyy-MM-dd'T'HH:mm:ss.SSSXXX (see: docs.oracle.com/javase/7/docs/api/java/text/…)Anthologize
Without any annotations it works but only if the offset is specified as Z. It won't work for +0100 or +01:00. I tried your format and I still get Unsupported field: YearOfEra exception, indicating that deserializer's formatter is not configured with a time zone (for some reason java 8 fromatter requires TZ even though the offset is specified in the string and the Instant is fully defined).Croaker
which version of jackson / jackson-datatype-jsr310 you are using?Anthologize
Jackson 2.6.5 and jackson-datatype-jsr310 2.6.5Croaker
Ok - I noticed that there's a separate problem with how I annotated the class (I'm actually using Kotlin which has annotation use-sites which I didn't use correctly). Anyhow, when trying your format string, it worked. Please copy your comment with your pattern to the answer so I can accept it.Croaker
A
82

You need to set the explicit time zone via XXX in your modell class:

@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")

(see: https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html)

Anthologize answered 27/3, 2016 at 22:49 Comment(6)
Using the suggested format along with placing annotation at correct place resolved the issue.Croaker
Date is part of the terrible legacy date-time classes that were years ago supplanted by the modern java.time classes. Specifically, java.util.Date was replaced by java.time.Instant, the class requested in this Question.Millwater
Somewhy it fails for me with Cannot construct instance of java.text.SimpleDateFormat, problem: Illegal pattern character 'T' Changing type to Date and keeping @JsonFormat(shape = JsonFormat.Shape.STRING) helped me, thanks to Guss answerMegilp
Thanks, this helped me a lot. I suggest making the millisecond part optional though: yyyy-MM-dd'T'HH:mm:ss[.SSS]XXXBarrault
Excellent, @JoostLambregts! Been searching for this! Thank you!Lovesick
One more thing helped me is making sure that JsonFormat is from fasterxml "import com.fasterxml.jackson.annotation.JsonFormat;"Sou
C
21

In Jackson 2.9.8 (current one as I'm writing this) it's better to use Instant instead of Date.

You have to add a dependency:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.9.8</version>
</dependency> 

Also, register the module and configure SerializationFeature.WRITE_DATES_AS_TIMESTAMPS to false.

new ObjectMapper()
                .findAndRegisterModules()
                .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

More information about Jackson for Java8 here: https://github.com/FasterXML/jackson-modules-java8

Contrivance answered 12/2, 2019 at 16:26 Comment(1)
This tweak the whole configuration!Superabound
V
19

If you want to serialize Date objects into ISO-8601, you don't need to specify a pattern at all - ISO-8601 is the default pattern. It is kind of mentioned in the JsonFormat Java doc:

Common uses include choosing between alternate representations -- for example, whether Date is to be serialized as number (Java timestamp) or String (such as ISO-8601 compatible time value) -- as well as configuring exact details with pattern() property.

[emphasasis mine] you should understand from the above text that specifying shape = STRING would mean an ISO-8601 format but you can choose something else using the pattern property.

In my experience, this always turns out a UTC date format (with the time zone rendered as +0000), which could be the default time zone in my VM (even though my operating system clock is not set to UTC).

Vespasian answered 24/10, 2018 at 21:7 Comment(4)
I think this question was about de-serializing, not serializing. But good to know anyway!Worm
It works the same for deserializing - @JsonFormat controls both. I use it like that both ways.Vespasian
Best answer so far IMHO. Easy and standard.Monitory
This does not work when deserializing a non-UTC ISO date-time. I tested it.Barrault
H
9

Jackson can be configured globally (without annotations) to accept timestamps with or without colon:

ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));

The default Jackson timezone format was changed since version 2.11 from '+0000' to '+00:00'. Both formats are valid according to ISO-8601.

Harakiri answered 5/8, 2020 at 19:45 Comment(1)
They're not both valid. One is basic format, the other extended ISO-8601 format. You cannot mix and match them.Envelop
S
4

The format "Z" does not work with "+01:00" as this is a different pattern. JsonFormat is using SimpleDateFormat patterns. "Z" in upper case only represents strict RFC 822. You have to use syntax like: "+0100", without colon.

See: ISO 8601:2004, SimpleDateFormat patterns

Shaman answered 24/6, 2016 at 7:39 Comment(0)
B
1

I was facing this issue while sending Instant data type variable on a RMQ queue. It was giving errors in different scenarios .

1st error that i encountered was

java.base/java.lang.Thread.run(Thread.java:829)\nCaused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type java.time.Instant not supported by default:

Solution was too add dependency of

    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jsr310</artifactId>
    </dependency>

Then second issue I started facing was that it was not able to serialise the date format . I tried to load JAVA TIME Modules with object mapper but that didn't help me. What worked for me is as below.

@JsonSerialize(using = InstantSerializer.class)
@JsonDeserialize(using = CustomInstantDeserializer.class)
Instant updateTime;

CustomeDeserialser i had to write because it was giving error that InvalidDefinitionException: Class com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer has no default (no arg) constructor\n

public class CustomInstantDeserializer extends 
 InstantDeserializer<Instant> { 
      public CustomInstantDeserializer() {
           super(InstantDeserializer.INSTANT,
            new DateTimeFormatterBuilder().
     parseCaseInsensitive()
   .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
                    .optionalStart().appendOffset("+HH:MM", 
  "+00:00").optionalEnd().optionalStart()
                    .appendOffset("+HHMM", 
    "+0000").optionalEnd().optionalStart().appendOffset("+HH", "Z")
                    .optionalEnd().toFormatter());
      }

    }
Bridewell answered 20/10, 2023 at 11:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.