Jackson Date

1. Overzicht

In deze zelfstudie serialiseren we datums met Jackson. We beginnen met het serialiseren van een eenvoudige java.util.Datum, dan Joda-Time en de Java 8 Datum Tijd.

2. Serialiseren Datum naar tijdstempel

Laten we eerst eens kijken hoe we een simple java.util.Date met Jackson.

In het volgende voorbeeld - we zullen een instantie van "Evenement”Die een Datum veld 'eventDate“:

@Test openbare leegte whenSerializingDateWithJackson_thenSerializedToTimestamp () gooit JsonProcessingException, ParseException {SimpleDateFormat df = nieuwe SimpleDateFormat ("dd-MM-jjjj uu: mm"); df.setTimeZone (TimeZone.getTimeZone ("UTC")); Datum datum = df.parse ("01-01-1970 01:00"); Evenement evenement = nieuw evenement ("feest", datum); ObjectMapper-mapper = nieuwe ObjectMapper (); mapper.writeValueAsString (evenement); }

Wat hier belangrijk is, is dat Jackson de datum standaard serialiseert naar een tijdstempelformaat (aantal milliseconden sinds 1 januari 1970, UTC).

De werkelijke output van de “evenement"Serialisatie is:

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

3. Serialiseren Datum volgens ISO-8601

Serialisatie naar dit korte tijdstempelformaat is niet optimaal. Laten we nu het Datum naar de ISO-8601 formaat:

@Test openbare leegte whenSerializingDateToISO8601_thenSerializedToText () gooit JsonProcessingException, ParseException {SimpleDateFormat df = nieuwe SimpleDateFormat ("dd-MM-jjjj uu: mm"); df.setTimeZone (TimeZone.getTimeZone ("UTC")); String toParse = "01-01-1970 02:30"; Datum datum = df.parse (toParse); Evenement evenement = nieuw evenement ("feest", datum); ObjectMapper-mapper = nieuwe ObjectMapper (); mapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // StdDateFormat is ISO8601 sinds jackson 2.9 mapper.setDateFormat (new StdDateFormat (). WithColonInTimeZone (true)); String resultaat = mapper.writeValueAsString (event); assertThat (resultaat, bevatString ("1970-01-01T02: 30: 00.000 + 00: 00")); }

Merk op hoe de weergave van de datum nu veel beter leesbaar is.

4. Configureer ObjectMapperDatumnotatie

De vorige oplossingen missen nog steeds de volledige flexibiliteit om het exacte formaat te kiezen om het java.util.Date gevallen.

Laten we nu eens kijken naar een configuratie waarmee we dat kunnen stel onze formaten in voor het weergeven van datums:

@Test openbare leegte whenSettingObjectMapperDateFormat_thenCorrect () gooit JsonProcessingException, ParseException {SimpleDateFormat df = nieuwe SimpleDateFormat ("dd-MM-jjjj uu: mm"); String toParse = "20-12-2014 02:30"; Datum datum = df.parse (toParse); Evenement evenement = nieuw evenement ("feest", datum); ObjectMapper-mapper = nieuwe ObjectMapper (); mapper.setDateFormat (df); String resultaat = mapper.writeValueAsString (event); assertThat (resultaat, bevatString (toParse)); }

Merk op dat, hoewel we nu flexibeler zijn met betrekking tot het datumnotatie, we nog steeds een globale configuratie gebruiken op het niveau van het geheel ObjectMapper.

5. Gebruik @JsonFormat om te formatteren Datum

Laten we vervolgens eens kijken naar het @JsonFormat annotatie bij controle van de datumnotatie op individuele klassen in plaats van globaal, voor de hele applicatie:

openbare klasse Gebeurtenis {openbare tekenreeksnaam; @JsonFormat (vorm = JsonFormat.Shape.STRING, patroon = "dd-MM-jjjj uu: mm: ss") openbare datum eventDate; }

Laten we het nu testen:

@Test openbare leegte whenUsingJsonFormatAnnotationToFormatDate_thenCorrect () gooit JsonProcessingException, ParseException {SimpleDateFormat df = nieuwe SimpleDateFormat ("dd-MM-jjjj uu: mm: ss"); df.setTimeZone (TimeZone.getTimeZone ("UTC")); String toParse = "20-12-2014 02:30:00"; Datum datum = df.parse (toParse); Evenement evenement = nieuw evenement ("feest", datum); ObjectMapper-mapper = nieuwe ObjectMapper (); String resultaat = mapper.writeValueAsString (event); assertThat (resultaat, bevatString (toParse)); }

6. Aangepast Datum Serializer

Vervolgens - om volledige controle over de uitvoer te krijgen, gebruiken we een aangepaste serialisator voor datums:

openbare klasse CustomDateSerializer breidt StdSerializer uit {privé SimpleDateFormat-formatter = nieuwe SimpleDateFormat ("dd-MM-jjjj uu: mm: ss"); openbare CustomDateSerializer () {dit (null); } openbare CustomDateSerializer (Klasse t) {super (t); } @Override public void serialize (datumwaarde, JsonGenerator gen, SerializerProvider arg2) gooit IOException, JsonProcessingException {gen.writeString (formatter.format (waarde)); }}

Vervolgens - laten we het gebruiken als de serialisator van onze "eventDate”Veld:

openbare klasse Gebeurtenis {openbare tekenreeksnaam; @JsonSerialize (using = CustomDateSerializer.class) public Date eventDate; }

Eindelijk - laten we het testen:

@Test openbare leegte whenUsingCustomDateSerializer_thenCorrect () gooit JsonProcessingException, ParseException {SimpleDateFormat df = nieuwe SimpleDateFormat ("dd-MM-jjjj uu: mm: ss"); String toParse = "20-12-2014 02:30:00"; Datum datum = df.parse (toParse); Evenement evenement = nieuw evenement ("feest", datum); ObjectMapper-mapper = nieuwe ObjectMapper (); String resultaat = mapper.writeValueAsString (event); assertThat (resultaat, bevatString (toParse)); }

7. Serialiseer Joda-Time met Jackson

Datums zijn niet altijd een voorbeeld van java.util.Date; eigenlijk - ze worden steeds meer vertegenwoordigd door een andere klasse - en een veel voorkomende is, natuurlijk, de Datum Tijd implementatie uit de Joda-Time-bibliotheek.

Laten we eens kijken hoe we dat kunnen doen serialiseren Datum Tijd met Jackson.

We maken gebruik van de jackson-datatype-joda module voor out-of-the-box Joda-Time ondersteuning:

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

En nu kunnen we eenvoudig het JodaModule en klaar:

@Test public void whenSerializingJodaTime_thenCorrect () gooit JsonProcessingException {DateTime date = new DateTime (2014, 12, 20, 2, 30, DateTimeZone.forID ("Europe / London")); ObjectMapper-mapper = nieuwe ObjectMapper (); mapper.registerModule (nieuwe JodaModule ()); mapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); String resultaat = mapper.writeValueAsString (datum); assertThat (resultaat, bevatString ("2014-12-20T02: 30: 00.000Z")); }

8. Serialiseer Joda Datum Tijd Met Custom Serializer

Als we de extra afhankelijkheid van Joda-Time Jackson niet willen, kunnen we er ook gebruik van maken een aangepaste serialisator (vergelijkbaar met de eerdere voorbeelden) om Datum Tijd exemplaren netjes geserialiseerd:

openbare klasse CustomDateTimeSerializer breidt StdSerializer uit {privé statische DateTimeFormatter-formatter = DateTimeFormat.forPattern ("jjjj-MM-dd HH: mm"); openbare CustomDateTimeSerializer () {dit (null); } openbare CustomDateTimeSerializer (Klasse t) {super (t); } @Override public void serialize (DateTime-waarde, JsonGenerator-gen, SerializerProvider arg2) gooit IOException, JsonProcessingException {gen.writeString (formatter.print (waarde)); }}

Vervolgens - laten we het gebruiken als ons eigendom "eventDate”Serialisator:

openbare klasse Gebeurtenis {openbare tekenreeksnaam; @JsonSerialize (using = CustomDateTimeSerializer.class) openbare DateTime eventDate; }

Eindelijk - laten we alles samenvoegen en testen:

@Test public void whenSerializingJodaTimeWithJackson_thenCorrect () gooit JsonProcessingException {DateTime date = new DateTime (2014, 12, 20, 2, 30); Evenement evenement = nieuw evenement ("feest", datum); ObjectMapper-mapper = nieuwe ObjectMapper (); String resultaat = mapper.writeValueAsString (event); assertThat (resultaat, bevatString ("2014-12-20 02:30")); }

9. Serialiseer Java 8 Datum Met Jackson

Laten we nu eens kijken hoe we Java 8 kunnen serialiseren Datum Tijd - in dit voorbeeld, LocalDateTime - Jackson gebruiken. We kunnen gebruik maken van de jackson-datatype-jsr310 module:

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

Nu hoeven we alleen nog maar het JavaTimeModule (JSR310Module is verouderd) en Jackson zorgt voor de rest:

@Test openbare leegte whenSerializingJava8Date_thenCorrect () gooit JsonProcessingException {LocalDateTime date = LocalDateTime.of (2014, 12, 20, 2, 30); ObjectMapper-mapper = nieuwe ObjectMapper (); mapper.registerModule (nieuwe JavaTimeModule ()); mapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); String resultaat = mapper.writeValueAsString (datum); assertThat (resultaat, bevatString ("2014-12-20T02: 30")); }

10. Serialiseer Java 8 Datum Zonder enige extra afhankelijkheid

Als u de extra afhankelijkheid niet wilt, kunt u altijd een aangepaste serialisator om de Java 8 uit te schrijven Datum Tijd naar JSON:

openbare klasse CustomLocalDateTimeSerializer breidt StdSerializer uit {privé statische DateTimeFormatter-formatter = DateTimeFormatter.ofPattern ("jjjj-MM-dd HH: mm"); openbare CustomLocalDateTimeSerializer () {dit (null); } openbare CustomLocalDateTimeSerializer (Klasse t) {super (t); } @Override public void serialize (LocalDateTime-waarde, JsonGenerator-gen, SerializerProvider arg2) gooit IOException, JsonProcessingException {gen.writeString (formatter.format (waarde)); }}

Vervolgens - laten we de serialisator gebruiken voor onze "eventDate”Veld:

openbare klasse Gebeurtenis {openbare tekenreeksnaam; @JsonSerialize (using = CustomLocalDateTimeSerializer.class) openbare LocalDateTime eventDate; }

Laten we het nu testen:

@Test openbare leegte whenSerializingJava8DateWithCustomSerializer_thenCorrect () gooit JsonProcessingException {LocalDateTime date = LocalDateTime.of (2014, 12, 20, 2, 30); Evenement evenement = nieuw evenement ("feest", datum); ObjectMapper-mapper = nieuwe ObjectMapper (); String resultaat = mapper.writeValueAsString (event); assertThat (resultaat, bevatString ("2014-12-20 02:30")); }

11. Deserialiseren Datum

Vervolgens - laten we eens kijken hoe we een Datum met Jackson. In het volgende voorbeeld - deserialiseren we een "Evenement”Instantie met een datum:

@Test public void whenDeserializingDateWithJackson_thenCorrect () gooit JsonProcessingException, IOException {String json = "{" name ":" party "," eventDate ":" 20-12-2014 02:30:00 "}"; SimpleDateFormat df = nieuwe SimpleDateFormat ("dd-MM-jjjj uu: mm: ss"); ObjectMapper-mapper = nieuwe ObjectMapper (); mapper.setDateFormat (df); Event event = mapper.readerFor (Event.class) .readValue (json); assertEquals ("20-12-2014 02:30:00", df.format (event.eventDate)); }

12. Deserialiseer Joda ZonedDateTime Met tijdzone behouden

In de standaardconfiguratie past Jackson de tijdzone van een Joda aan ZonedDateTime naar de tijdzone van de lokale context. Aangezien de tijdzone van de lokale context standaard niet is ingesteld en handmatig moet worden geconfigureerd, past Jackson de tijdzone aan naar GMT:

@Test openbare leegte whenDeserialisingZonedDateTimeWithDefaults_thenNotCorrect () gooit IOException {ObjectMapper objectMapper = new ObjectMapper (); objectMapper.findAndRegisterModules (); objectMapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); ZonedDateTime now = ZonedDateTime.now (ZoneId.of ("Europe / Berlin")); String geconverteerd = objectMapper.writeValueAsString (nu); ZonedDateTime hersteld = objectMapper.readValue (geconverteerd, ZonedDateTime.class); System.out.println ("serialized:" + nu); System.out.println ("hersteld:" + hersteld); assertThat (nu, is (hersteld)); }

Deze testcase zal mislukken met uitvoer:

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

Gelukkig is er een snelle en eenvoudige oplossing voor dit vreemde standaardgedrag: we hoeven het Jackson alleen maar te vertellen, de tijdzone niet aan te passen.

Dit kan worden gedaan door de onderstaande regel code toe te voegen aan de bovenstaande testcase:

objectMapper.disable (DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

Merk op dat, om de tijdzone te behouden, we ook het standaardgedrag van het serialiseren van de datum naar het tijdstempel moeten uitschakelen.

13. Aangepast Datum Deserializer

Laten we ook kijken hoe u een gewoonte Datum deserializer; we schrijven een aangepaste deserializer voor het onroerend goed "eventDate“:

openbare klasse CustomDateDeserializer breidt StdDeserializer uit {privé SimpleDateFormat-formatter = nieuwe SimpleDateFormat ("dd-MM-jjjj uu: mm: ss"); openbare CustomDateDeserializer () {dit (null); } openbare CustomDateDeserializer (klasse vc) {super (vc); } @Override public Date deserialize (JsonParser jsonparser, DeserializationContext context) gooit IOException, JsonProcessingException {String date = jsonparser.getText (); probeer {return formatter.parse (date); } catch (ParseException e) {throw nieuwe RuntimeException (e); }}}

Vervolgens - laten we het gebruiken als de "eventDate"Deserializer:

openbare klasse Gebeurtenis {openbare tekenreeksnaam; @JsonDeserialize (using = CustomDateDeserializer.class) public Date eventDate; }

En tot slot - laten we het testen:

@Test public void whenDeserializingDateUsingCustomDeserializer_thenCorrect () gooit JsonProcessingException, IOException {String json = "{" name ":" party "," eventDate ":" 20-12-2014 02:30:00 "}"; SimpleDateFormat df = nieuwe SimpleDateFormat ("dd-MM-jjjj uu: mm: ss"); ObjectMapper-mapper = nieuwe ObjectMapper (); Event event = mapper.readerFor (Event.class) .readValue (json); assertEquals ("20-12-2014 02:30:00", df.format (event.eventDate)); }

14. Bevestiging Ongeldige definitieUitzondering

Bij het maken van een LocalDate we kunnen bijvoorbeeld een uitzondering tegenkomen:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: kan geen instantie van `java.time.LocalDate` construeren (er bestaan ​​geen makers, zoals standaardconstructie): geen constructor met String-argument / fabrieksmethode om te deserialiseren van String-waarde ('2014 -12-20 ') bij [Bron: (String) "2014-12-20"; regel: 1, kolom: 1]

Dit probleem treedt op omdat JSON niet standaard een datumnotatie heeft, dus datums als Draad.

De Draad weergave van een datum is niet hetzelfde als een object van type LocalDate in het geheugen, dus we hebben een externe deserializer nodig om dat veld uit een Draad, en een serializer om de datum naar weer te geven Draad formaat.

Deze methoden zijn ook van toepassing op LocalDateTime - de enige verandering is om een ​​gelijkwaardige klasse te gebruiken voor LocalDateTime.

14.1. Jackson afhankelijkheid

Jackson stelt ons in staat dit op een aantal manieren op te lossen. Ten eerste moeten we ervoor zorgen dat het jsr310 afhankelijkheid zit in ons pom.xml:

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

14.2. Serialisatie naar Single Date-object

Om te kunnen omgaan LocalDate, moeten we het JavaTimeModule met onze ObjectMapper.

We schakelen de functie ook uit WRITE_DATES_AS_TIMESTAMPS in ObjectMapper om te voorkomen dat Jackson tijdcijfers aan de JSON-uitvoer toevoegt:

@Test public void whenSerializingJava8DateAndReadingValue_thenCorrect () gooit IOException {String stringDate = "\" 2014-12-20 \ ""; ObjectMapper-mapper = nieuwe ObjectMapper (); mapper.registerModule (nieuwe JavaTimeModule ()); mapper.disable (SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); LocalDate resultaat = mapper.readValue (stringDate, LocalDate.class); assertThat (result.toString (), containsString ("2014-12-20")); }

Hier hebben we de native ondersteuning van Jackson gebruikt voor het serialiseren en deserialiseren van datums.

14.3. Annotatie in POJO

Een andere manier om met dat probleem om te gaan, is door de LocalDateDeserializer en JsonFormat annotaties op entiteitsniveau:

openbare klasse EventWithLocalDate {@JsonDeserialize (using = LocalDateDeserializer.class) @JsonSerialize (met = LocalDateSerializer.class) @JsonFormat (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy") public LocalDate eventDate; }

De @JsonDeserialize annotatie wordt gebruikt om een ​​aangepaste deserializer op te geven om het JSON-object ongedaan te maken. Evenzo @JsonSerialize geeft een aangepaste serialisator aan die moet worden gebruikt bij het rangschikken van de entiteit.

Bovendien is de annotatie @JsonFormat stelt ons in staat om het formaat te specificeren waarnaar we datumwaarden zullen serialiseren. Daarom kan deze POJO worden gebruikt om de JSON te lezen en te schrijven:

@Test public void whenSerializingJava8DateAndReadingFromEntity_thenCorrect () gooit IOException {String json = "{\" name \ ": \" party \ ", \" eventDate \ ": \" 20-12-2014 \ "}"; ObjectMapper-mapper = nieuwe ObjectMapper (); EventWithLocalDate resultaat = mapper.readValue (json, EventWithLocalDate.class); assertThat (result.getEventDate (). toString (), containsString ("2014-12-20")); }

Hoewel deze aanpak meer werk kost dan het gebruik van de JavaTimeModule standaardinstellingen, het is veel meer aanpasbaar.

15. Conclusie

In deze uitgebreide Datum artikel hebben we op verschillende manieren gekeken Jackson kan helpen om een ​​date naar JSON te ordenen en ongemarkeerd te krijgen met een verstandig formaat waarover we controle hebben.

Zoals altijd is de voorbeeldcode te vinden op GitHub.