Jackson Date

1. Oversigt

I denne vejledning serier vi datoer med Jackson. Vi starter med at serieisere en simpel java.util.Datoderefter Joda-Time såvel som Java 8 Dato tid.

2. Serialiser Dato til tidsstempel

Først - lad os se, hvordan vi serierer en simpel java.util.Date med Jackson.

I det følgende eksempel - serier vi en forekomst af “Begivenhed”Som har en Dato Mark "eventDate“:

@Test offentlig ugyldigt nårSerializingDateWithJackson_thenSerializedToTimestamp () kaster JsonProcessingException, ParseException {SimpleDateFormat df = ny SimpleDateFormat ("dd-MM-åååå hh: mm"); df.setTimeZone (TimeZone.getTimeZone ("UTC")); Dato dato = df.parse ("01-01-1970 01:00"); Begivenhedsbegivenhed = ny begivenhed ("fest", dato); ObjectMapper-kortlægger = ny ObjectMapper (); mapper.writeValueAsString (begivenhed); }

Hvad der er vigtigt her er, at Jackson serierer datoen til et tidsstempelformat som standard (antal millisekunder siden 1. januar 1970, UTC).

Den faktiske produktion af “begivenhed”Serialisering er:

{"name": "party", "eventDate": 3600000}

3. Serialiser Dato til ISO-8601

Serialisering til dette korte tidsstempelformat er ikke optimalt. Lad os nu serialisere Dato til ISO-8601 format:

@Test offentlig ugyldigt nårSerializingDateToISO8601_thenSerializedToText () kaster JsonProcessingException, ParseException {SimpleDateFormat df = ny SimpleDateFormat ("dd-MM-åååå hh: mm"); df.setTimeZone (TimeZone.getTimeZone ("UTC")); String toParse = "01-01-1970 02:30"; Dato dato = df.parse (toParse); Begivenhedsbegivenhed = ny begivenhed ("fest", dato); ObjectMapper-kortlægger = ny ObjectMapper (); mapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // StdDateFormat er ISO8601 siden jackson 2.9 mapper.setDateFormat (ny StdDateFormat (). MedColonInTimeZone (sand)); String result = mapper.writeValueAsString (event); assertThat (resultat, containString ("1970-01-01T02: 30: 00.000 + 00: 00")); }

Bemærk, hvordan repræsentationen af ​​datoen nu er meget mere læsbar.

4. Konfigurer ObjectMapperDatoformat

De tidligere løsninger mangler stadig den fulde fleksibilitet ved at vælge det nøjagtige format til at repræsentere java.util.Date tilfælde.

Lad os nu se på en konfiguration, der giver os mulighed for det indstil vores formater til at repræsentere datoer:

@Test offentlig ugyldig nårSettingObjectMapperDateFormat_thenCorrect () kaster JsonProcessingException, ParseException {SimpleDateFormat df = ny SimpleDateFormat ("dd-MM-åååå hh: mm"); String toParse = "20-12-2014 02:30"; Dato dato = df.parse (toParse); Begivenhedsbegivenhed = ny begivenhed ("fest", dato); ObjectMapper-kortlægger = ny ObjectMapper (); mapper.setDateFormat (df); String result = mapper.writeValueAsString (event); assertThat (resultat, indeholderString (toParse)); }

Bemærk, at selvom vi nu er mere fleksible med hensyn til datoformatet, bruger vi stadig en global konfiguration på niveau med hele ObjectMapper.

5. Brug @JsonFormat til Format Dato

Lad os derefter se på @JsonFormat kommentar til kontrollere datoformatet på individuelle klasser i stedet for globalt for hele applikationen:

public class Event {public String name; @JsonFormat (form = JsonFormat.Shape.STRING, mønster = "dd-MM-åååå tt: mm: ss") offentlig Dato eventDate; }

Nu - lad os teste det:

@Test offentlig ugyldig, nårUsingJsonFormatAnnotationToFormatDate_thenCorrect () kaster JsonProcessingException, ParseException {SimpleDateFormat df = ny SimpleDateFormat ("dd-MM-åååå hh: mm: ss"); df.setTimeZone (TimeZone.getTimeZone ("UTC")); String toParse = "20-12-2014 02:30:00"; Dato dato = df.parse (toParse); Begivenhedsbegivenhed = ny begivenhed ("fest", dato); ObjectMapper-kortlægger = ny ObjectMapper (); String result = mapper.writeValueAsString (event); assertThat (resultat, indeholderString (toParse)); }

6. Brugerdefineret Dato Serializer

Dernæst - for at få fuld kontrol over output, udnytter vi en brugerdefineret serializer til datoer:

offentlig klasse CustomDateSerializer udvider StdSerializer {private SimpleDateFormat formatter = ny SimpleDateFormat ("dd-MM-åååå hh: mm: ss"); offentlig CustomDateSerializer () {dette (null); } offentlig CustomDateSerializer (klasse t) {super (t); } @ Override public void serialize (Datoværdi, JsonGenerator gen, SerializerProvider arg2) kaster IOException, JsonProcessingException {gen.writeString (formatter.format (værdi)); }}

Dernæst - lad os bruge det som serialisering af vores “eventDate" Mark:

public class Event {public String name; @JsonSerialize (ved hjælp af = CustomDateSerializer.class) offentlig dato eventDate; }

Endelig - lad os teste det:

@Test offentligt ugyldigt nårUsingCustomDateSerializer_thenCorrect () kaster JsonProcessingException, ParseException {SimpleDateFormat df = ny SimpleDateFormat ("dd-MM-åååå hh: mm: ss"); String toParse = "20-12-2014 02:30:00"; Dato dato = df.parse (toParse); Begivenhedsbegivenhed = ny begivenhed ("fest", dato); ObjectMapper-kortlægger = ny ObjectMapper (); String result = mapper.writeValueAsString (event); assertThat (resultat, indeholderString (toParse)); }

7. Serialiser Joda-Time With Jackson

Datoer er ikke altid en forekomst af java.util.Date; faktisk - de er mere og mere repræsenteret af en anden klasse - og en almindelig er naturligvis Dato tid implementering fra Joda-Time-biblioteket.

Lad os se, hvordan vi kan serialisere Dato tid med Jackson.

Vi gør brug af jackson-datatype-joda modul til out-of-the-box Joda-Time support:

 com.fasterxml.jackson.datatype jackson-datatype-joda 2.9.7 

Og nu kan vi blot registrere JodaModule og gør det:

@Test offentlig ugyldig nårSerializingJodaTime_thenCorrect () kaster JsonProcessingException {DateTime date = new DateTime (2014, 12, 20, 2, 30, DateTimeZone.forID ("Europe / London")); ObjectMapper-kortlægger = ny ObjectMapper (); mapper.registerModule (ny JodaModule ()); mapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); String result = mapper.writeValueAsString (date); assertThat (resultat, containString ("2014-12-20T02: 30: 00.000Z")); }

8. Serialiser Joda Dato tid Med Custom Serializer

Hvis vi ikke ønsker den ekstra afhængighed af Joda-Time Jackson - kan vi også gøre brug af det en tilpasset serializer (svarende til de tidligere eksempler) at få Dato tid tilfælde serialiseret rent:

offentlig klasse CustomDateTimeSerializer udvider StdSerializer {privat statisk DateTimeFormatter formatter = DateTimeFormat.forPattern ("åååå-MM-dd HH: mm"); offentlig CustomDateTimeSerializer () {dette (null); } offentlig CustomDateTimeSerializer (klasse t) {super (t); } @ Override public void serialize (DateTime-værdi, JsonGenerator gen, SerializerProvider arg2) kaster IOException, JsonProcessingException {gen.writeString (formatter.print (værdi)); }}

Næste - lad os bruge det som vores ejendom “eventDate”Serializer:

public class Event {public String name; @JsonSerialize (ved hjælp af = CustomDateTimeSerializer.class) offentlig DateTime eventDate; }

Endelig - lad os sætte alt sammen og teste det:

@Test offentlig ugyldig nårSerializingJodaTimeWithJackson_thenCorrect () kaster JsonProcessingException {DateTime date = new DateTime (2014, 12, 20, 2, 30); Begivenhedsbegivenhed = ny begivenhed ("fest", dato); ObjectMapper-kortlægger = ny ObjectMapper (); String result = mapper.writeValueAsString (event); assertThat (resultat, containString ("2014-12-20 02:30")); }

9. Serialiser Java 8 Dato Med Jackson

Dernæst - lad os se, hvordan vi serialiserer Java 8 Dato tid - i dette eksempel LocalDateTime - ved hjælp af Jackson. Vi kan gøre brug af jackson-datatype-jsr310 modul:

 com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.9.7 

Nu er alt, hvad vi skal gøre, at registrere JavaTimeModule (JSR310Modul er udfaset) og Jackson tager sig af resten:

@Test offentlig ugyldig nårSerializingJava8Date_thenCorrect () kaster JsonProcessingException {LocalDateTime date = LocalDateTime.of (2014, 12, 20, 2, 30); ObjectMapper-kortlægger = ny ObjectMapper (); mapper.registerModule (ny JavaTimeModule ()); mapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); String result = mapper.writeValueAsString (date); assertThat (resultat, containString ("2014-12-20T02: 30")); }

10. Serialiser Java 8 Dato Uden ekstra afhængighed

Hvis du ikke vil have den ekstra afhængighed, kan du altid bruge en brugerdefineret serializer til at skrive Java 8 ud Dato tid til JSON:

offentlig klasse CustomLocalDateTimeSerializer udvider StdSerializer {privat statisk DateTimeFormatter formatter = DateTimeFormatter.ofPattern ("åååå-MM-dd HH: mm"); offentlig CustomLocalDateTimeSerializer () {dette (null); } offentlig CustomLocalDateTimeSerializer (klasse t) {super (t); } @ Override public void serialize (LocalDateTime-værdi, JsonGenerator gen, SerializerProvider arg2) kaster IOException, JsonProcessingException {gen.writeString (formatter.format (værdi)); }}

Næste - lad os bruge serializer til vores “eventDate" Mark:

public class Event {public String name; @JsonSerialize (ved hjælp af = CustomLocalDateTimeSerializer.class) offentlig LocalDateTime eventDate; }

Nu - lad os teste det:

@Test offentlig ugyldig nårSerializingJava8DateWithCustomSerializer_thenCorrect () kaster JsonProcessingException {LocalDateTime date = LocalDateTime.of (2014, 12, 20, 2, 30); Begivenhedsbegivenhed = ny begivenhed ("fest", dato); ObjectMapper-kortlægger = ny ObjectMapper (); String result = mapper.writeValueAsString (event); assertThat (resultat, containString ("2014-12-20 02:30")); }

11. Deserialiser Dato

Næste - lad os se, hvordan man deserialiserer en Dato med Jackson. I det følgende eksempel - deserialiserer vi et ”Begivenhed”Forekomst indeholdende en dato:

@Test offentlig ugyldig nårDeserializingDateWithJackson_thenCorrect () kaster JsonProcessingException, IOException {String json = "{" name ":" party "," eventDate ":" 20-12-2014 02:30:00 "}"; SimpleDateFormat df = ny SimpleDateFormat ("dd-MM-åååå hh: mm: ss"); ObjectMapper-kortlægger = ny ObjectMapper (); mapper.setDateFormat (df); Event event = mapper.readerFor (Event.class) .readValue (json); assertEquals ("20-12-2014 02:30:00", df.format (event.eventDate)); }

12. Deserialiser Joda ZonedDateTime Med tidszone bevaret

I sin standardkonfiguration justerer Jackson tidszonen for en Joda ZonedDateTime til tidszonen for den lokale kontekst. Da tidszonen for den lokale kontekst som standard ikke er indstillet og skal konfigureres manuelt, justerer Jackson tidszonen til GMT:

@Test offentlig ugyldig nårDeserialisingZonedDateTimeWithDefaults_thenNotCorrect () kaster IOException {ObjectMapper objectMapper = ny ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); ZonedDateTime nu = ZonedDateTime.now (ZoneId.of ("Europa / Berlin")); Streng konverteret = objectMapper.writeValueAsString (nu); ZonedDateTime gendannet = objectMapper.readValue (konverteret, ZonedDateTime.class); System.out.println ("serialiseret:" + nu); System.out.println ("gendannet:" + gendannet); assertThat (nu er (gendannet)); }

Denne testsag mislykkes med output:

serialiseret: 2017-08-14T13: 52: 22.071 + 02: 00 [Europe / Berlin] gendannet: 2017-08-14T11: 52: 22.071Z [UTC]

Heldigvis er der en hurtig og enkel løsning på denne ulige standardadfærd: vi skal bare fortælle Jackson, at han ikke skal justere tidszonen.

Dette kan gøres ved at tilføje nedenstående kodelinje til ovenstående testtilfælde:

objectMapper.disable (DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

Bemærk, at for at bevare tidszonen er vi også nødt til at deaktivere standardopførelsen ved at serialisere datoen til tidsstemplet.

13. Brugerdefineret Dato Deserializer

Lad os også se, hvordan man bruger en skik Dato deserializer; vi skriver en brugerdefineret deserializer til ejendommen “eventDate“:

offentlig klasse CustomDateDeserializer udvider StdDeserializer {private SimpleDateFormat formatter = ny SimpleDateFormat ("dd-MM-åååå hh: mm: ss"); offentlig CustomDateDeserializer () {dette (null); } offentlig CustomDateDeserializer (klasse vc) {super (vc); } @ Override public Dato deserialize (JsonParser jsonparser, DeserializationContext context) kaster IOException, JsonProcessingException {String date = jsonparser.getText (); prøv {return formatter.parse (dato); } fange (ParseException e) {kast ny RuntimeException (e); }}}

Næste - lad os bruge det som “eventDate”Deserializer:

public class Event {public String name; @JsonDeserialize (ved hjælp af = CustomDateDeserializer.class) offentlig dato eventDate; }

Og endelig - lad os teste det:

@Test offentlig ugyldig nårDeserializingDateUsingCustomDeserializer_thenCorrect () kaster JsonProcessingException, IOException {String json = "{" name ":" party "," eventDate ":" 20-12-2014 02:30:00 "}"; SimpleDateFormat df = ny SimpleDateFormat ("dd-MM-åååå hh: mm: ss"); ObjectMapper-kortlægger = ny ObjectMapper (); Event event = mapper.readerFor (Event.class) .readValue (json); assertEquals ("20-12-2014 02:30:00", df.format (event.eventDate)); }

14. Fastsættelse InvalidDefinitionUndtagelse

Når du opretter en LocalDate for eksempel kan vi støde på en undtagelse:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Kan ikke konstruere forekomst af 'java.time.LocalDate' (der findes ingen skabere, som standardkonstruktion): ingen strengargumentkonstruktør / fabriksmetode til deserialisering fra strengværdi ('2014 -12-20 ') ved [Kilde: (String) "2014-12-20"; linje: 1, kolonne: 1]

Dette problem opstår, fordi JSON ikke har et datoformat, så det repræsenterer datoer som Snor.

Det Snor repræsentation af en dato er ikke det samme som et objekt af typen LocalDate i hukommelsen, så vi har brug for en ekstern deserializer til at læse dette felt fra en Snor, og en serializer, der skal gengives datoen til Snor format.

Disse metoder gælder også for LocalDateTime - den eneste ændring er at bruge en tilsvarende klasse til LocalDateTime.

14.1. Jackson afhængighed

Jackson giver os mulighed for at løse dette på et par måder. Først skal vi sørge for, at jsr310 afhængighed er i vores pom.xml:

 com.fasterxml.jackson.datatype jackson-datatype-jsr310 2.11.0 

14.2. Serialisering til enkelt dato-objekt

For at være i stand til at håndtere LocalDate, skal vi registrere JavaTimeModule med vores ObjectMapper.

Vi deaktiverer også funktionen WRITE_DATES_AS_TIMESTAMPS i ObjectMapper for at forhindre Jackson i at tilføje tidscifre til JSON-output:

@Test offentlig ugyldig når SerializingJava8DateAndReadingValue_thenCorrect () kaster IOException {String stringDate = "\" 2014-12-20 \ ""; ObjectMapper-kortlægger = ny ObjectMapper (); mapper.registerModule (ny JavaTimeModule ()); mapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); LocalDate-resultat = mapper.readValue (stringDate, LocalDate.class); assertThat (result.toString (), containString ("2014-12-20")); }

Her har vi brugt Jacksons oprindelige support til serialisering og deserialisering af datoer.

14.3. Kommentar i POJO

En anden måde at tackle dette problem er at bruge LocalDateDeserializer og JsonFormat annoteringer på enhedsniveau:

offentlig klasse EventWithLocalDate {@JsonDeserialize (ved hjælp af = LocalDateDeserializer.class) @JsonSerialize (ved hjælp af = LocalDateSerializer.class) @JsonFormat (form = JsonFormat.Shape.STRING, mønster = "dd-MM-yyyyy") offentlig LocalDate eventDate; }

Det @JsonDeserialize annotering bruges til at angive en brugerdefineret deserializer til at fjerne JSON-objektet. Tilsvarende @JsonSerialize angiver en brugerdefineret serialiseringsenhed, der skal bruges ved marskalering af enheden.

Derudover kommentaren @JsonFormat giver os mulighed for at specificere det format, som vi serierer datoværdier til. Derfor kan denne POJO bruges til at læse og skrive JSON:

@Test offentlig ugyldig nårSerializingJava8DateAndReadingFromEntity_thenCorrect () kaster IOException {String json = "{\" name \ ": \" party \ ", \" eventDate \ ": \" 20-12-2014 \ "}"; ObjectMapper-kortlægger = ny ObjectMapper (); EventWithLocalDate result = mapper.readValue (json, EventWithLocalDate.class); assertThat (result.getEventDate (). toString (), indeholderString ("2014-12-20")); }

Mens denne tilgang kræver mere arbejde end at bruge JavaTimeModule standardindstillinger, det er meget mere tilpasses.

15. Konklusion

I dette omfattende Dato artikel så vi på flere måder Jackson kan hjælpe marshal og unmarshal en date til JSON ved hjælp af et fornuftigt format, som vi har kontrol over.

Som altid kan eksemplerne findes på GitHub.