Jackson Date – 杰克逊日期

最后修改: 2014年 12月 27日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll serialize dates with Jackson. We’ll start by serializing a simple java.util.Date, then Joda-Time, and finally, the Java 8 DateTime.

在本教程中,我们将用Jackson对日期进行序列化。我们将从序列化一个简单的java.util.Date开始,然后是Joda-Time,最后是Java 8 DateTime

2. Serialize Date to Timestamp

2.将Date序列化为时间戳

First, let’s see how to serialize a simple java.util.Date with Jackson.

首先,让我们看看如何用Jackson对一个简单的java.util.Date进行序列化

In the following example, we’ll serialize an instance of “Event,” which has the Date field “eventDate“:

在下面的例子中,我们将序列化一个”Event,“的实例,它有Date字段”eventDate“。

@Test
public void whenSerializingDateWithJackson_thenSerializedToTimestamp()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));

    Date date = df.parse("01-01-1970 01:00");
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    mapper.writeValueAsString(event);
}

It’s important to note that Jackson will serialize the Date to a timestamp format by default (number of milliseconds since January 1st, 1970, UTC).

值得注意的是,Jackson将默认把Date序列化为时间戳格式(自1970年1月1日起的毫秒数,UTC)。

The actual output of the “event” serialization is:

event“序列化的实际输出是。

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

3. Serialize Date to ISO-8601

3.将Date序列化为ISO-8601

Serializing to this terse timestamp format is not optimal. Instead, let’s serialize the Date to the ISO-8601 format:

序列化为这种简明的时间戳格式并不理想。相反,让我们将Date序列化为ISO-8601格式。

@Test
public void whenSerializingDateToISO8601_thenSerializedToText()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));

    String toParse = "01-01-1970 02:30";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    // StdDateFormat is ISO8601 since jackson 2.9
    mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString("1970-01-01T02:30:00.000+00:00"));
}

We can see that the representation of the date is now much more readable.

我们可以看到,现在日期的表示方法更易读了。

4. Configure ObjectMapper DateFormat

4.配置ObjectMapper DateFormat

The previous solutions still lack the full flexibility of choosing the exact format to represent the java.util.Date instances.

之前的解决方案仍然缺乏选择确切格式来表示java.util.Date实例的充分灵活性。

Conversely, let’s take a look at a configuration that will allow us to set our formats for representing dates:

相反,让我们看看一个配置,它将允许我们设置我们代表日期的格式

@Test
public void whenSettingObjectMapperDateFormat_thenCorrect()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");

    String toParse = "20-12-2014 02:30";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    mapper.setDateFormat(df);

    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

Note that, even though we’re now more flexible regarding the date format, we’re still using a global configuration at the level of the entire ObjectMapper.

请注意,即使我们现在在日期格式方面更加灵活,我们仍然在整个ObjectMapper层面上使用全局配置。

5. Use @JsonFormat to Format Date

5.使用@JsonFormat来格式化Date

Next let’s take a look at the @JsonFormat annotation to control the date format on individual classes, instead of globally, for the entire application:

接下来让我们看看@JsonFormat注解,以控制单个类的日期格式,而不是全局的,为整个应用程序。

public class Event {
    public String name;

    @JsonFormat
      (shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
    public Date eventDate;
}

Now let’s test it:

现在我们来测试一下。

@Test
public void whenUsingJsonFormatAnnotationToFormatDate_thenCorrect()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    df.setTimeZone(TimeZone.getTimeZone("UTC"));

    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

6. Custom Date Serializer

6.自定义日期串行器

Next, to get full control over the output, we’ll leverage a custom serializer for Dates:

接下来,为了获得对输出的完全控制,我们将利用一个自定义的Dates串行器。

public class CustomDateSerializer extends StdSerializer<Date> {
 
    private SimpleDateFormat formatter 
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateSerializer() {
        this(null);
    }

    public CustomDateSerializer(Class t) {
        super(t);
    }
    
    @Override
    public void serialize (Date value, JsonGenerator gen, SerializerProvider arg2)
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.format(value));
    }
}

Now we’ll use it as the serializer of our “eventDate” field:

现在我们将使用它作为”eventDate“字段的序列化器。

public class Event {
    public String name;

    @JsonSerialize(using = CustomDateSerializer.class)
    public Date eventDate;
}

Finally, we’ll test it:

最后,我们将测试它。

@Test
public void whenUsingCustomDateSerializer_thenCorrect()
  throws JsonProcessingException, ParseException {
 
    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    String toParse = "20-12-2014 02:30:00";
    Date date = df.parse(toParse);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString(toParse));
}

7. Serialize Joda-Time With Jackson

7.用Jackson将Joda-Time序列化

Dates aren’t always an instance of java.util.Date. In fact, more and more, dates are represented by some other class, and a common one is the DateTime implementation from the Joda-Time library.

日期并不总是java.util.Date.的实例,事实上,越来越多的日期是由其他类来表示的,常见的是Joda-Time库中的DateTime实现。

Let’s see how we can serialize DateTime with Jackson.

让我们看看如何用Jackson 序列化DateTime

We’ll make use of the jackson-datatype-joda module for out-of-the-box Joda-Time support:

我们将利用 jackson-datatype-joda模块来获得开箱即用的Joda-Time支持。

<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-joda</artifactId>
  <version>2.9.7</version>
</dependency>

Then we can simply register the JodaModule and be done:

然后我们可以简单地注册JodaModule就可以了。

@Test
public void whenSerializingJodaTime_thenCorrect() 
  throws JsonProcessingException {
    DateTime date = new DateTime(2014, 12, 20, 2, 30, 
      DateTimeZone.forID("Europe/London"));

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JodaModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    String result = mapper.writeValueAsString(date);
    assertThat(result, containsString("2014-12-20T02:30:00.000Z"));
}

8. Serialize Joda DateTime With Custom Serializer

8.用自定义序列化器序列化Joda DateTime

If we don’t want the extra Joda-Time Jackson dependency, we can also make use of a custom serializer (similar to the earlier examples) to get DateTime instances serialized cleanly:

如果我们不想要额外的Joda-Time Jackson依赖,我们也可以利用自定义序列化器(类似于前面的例子)来获得DateTime实例的干净序列化。

public class CustomDateTimeSerializer extends StdSerializer<DateTime> {

    private static DateTimeFormatter formatter = 
      DateTimeFormat.forPattern("yyyy-MM-dd HH:mm");

    public CustomDateTimeSerializer() {
        this(null);
    }

     public CustomDateTimeSerializer(Class<DateTime> t) {
         super(t);
     }
    
    @Override
    public void serialize
      (DateTime value, JsonGenerator gen, SerializerProvider arg2)
      throws IOException, JsonProcessingException {
        gen.writeString(formatter.print(value));
    }
}

Then we can use it as our property “eventDate” serializer:

然后我们可以用它作为我们的属性”eventDate“序列化器。

public class Event {
    public String name;

    @JsonSerialize(using = CustomDateTimeSerializer.class)
    public DateTime eventDate;
}

Finally, we can put everything together and test it:

最后,我们可以把所有东西放在一起并进行测试。

@Test
public void whenSerializingJodaTimeWithJackson_thenCorrect() 
  throws JsonProcessingException {
 
    DateTime date = new DateTime(2014, 12, 20, 2, 30);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString("2014-12-20 02:30"));
}

9. Serialize Java 8 Date With Jackson

9.用Jackson对Java 8的Date进行序列化

Now let’s see how to serialize Java 8 DateTime, in this example LocalDateTime, using Jackson. We can make use of the jackson-datatype-jsr310 module:

现在让我们看看如何使用Jackson来序列化Java 8的DateTime,在这个例子中LocalDateTime。我们可以利用 jackson-datatype-jsr310模块。

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

Then all we need to do is register the JavaTimeModule (JSR310Module is deprecated), and Jackson will take care of the rest:

那么我们需要做的就是注册JavaTimeModuleJSR310Module已被弃用),而Jackson会处理剩下的事情。

@Test
public void whenSerializingJava8Date_thenCorrect()
  throws JsonProcessingException {
    LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30);

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    String result = mapper.writeValueAsString(date);
    assertThat(result, containsString("2014-12-20T02:30"));
}

10. Serialize Java 8 Date Without Any Extra Dependency

10.串行化Java 8的Date,没有任何额外的依赖

If we don’t want the extra dependency, we can always use a custom serializer to write out the Java 8 DateTime to JSON:

如果我们不想要额外的依赖性,我们总是可以使用一个自定义的序列化器,将Java 8的DateTime写成JSON

public class CustomLocalDateTimeSerializer 
  extends StdSerializer<LocalDateTime> {

    private static DateTimeFormatter formatter = 
      DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");

    public CustomLocalDateTimeSerializer() {
        this(null);
    }
 
    public CustomLocalDateTimeSerializer(Class<LocalDateTime> t) {
        super(t);
    }
    
    @Override
    public void serialize(
      LocalDateTime value,
      JsonGenerator gen,
      SerializerProvider arg2)
      throws IOException, JsonProcessingException {
 
        gen.writeString(formatter.format(value));
    }
}

Then we’ll use the serializer for our “eventDate” field:

然后我们将对我们的”eventDate“字段使用序列化器。

public class Event {
    public String name;

    @JsonSerialize(using = CustomLocalDateTimeSerializer.class)
    public LocalDateTime eventDate;
}

Finally, we’ll test it:

最后,我们将测试它。

@Test
public void whenSerializingJava8DateWithCustomSerializer_thenCorrect()
  throws JsonProcessingException {
 
    LocalDateTime date = LocalDateTime.of(2014, 12, 20, 2, 30);
    Event event = new Event("party", date);

    ObjectMapper mapper = new ObjectMapper();
    String result = mapper.writeValueAsString(event);
    assertThat(result, containsString("2014-12-20 02:30"));
}

11. Deserialize Date

11.反序列化Date

Now let’s see how we can deserialize a Date with Jackson. In the following example, we’ll deserialize an “Event” instance containing a date:

现在让我们看看我们如何用Jackson来反序列化一个Date。在下面的例子中,我们将反序列化一个包含日期的”Event“实例。

@Test
public void whenDeserializingDateWithJackson_thenCorrect()
  throws JsonProcessingException, IOException {
 
    String json = "{"name":"party","eventDate":"20-12-2014 02:30:00"}";

    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    ObjectMapper mapper = new ObjectMapper();
    mapper.setDateFormat(df);

    Event event = mapper.readerFor(Event.class).readValue(json);
    assertEquals("20-12-2014 02:30:00", df.format(event.eventDate));
}

12. Deserialize Joda ZonedDateTime With Time Zone Preserved

12.反序列化Joda的ZonedDateTime,保留时区

In its default configuration, Jackson adjusts the time zone of a Joda ZonedDateTime to the time zone of the local context. Since the time zone of the local context is not set by default, and has to be configured manually, Jackson adjusts the time zone to GMT:

在其默认配置中,Jackson将Joda ZonedDateTime的时区调整为本地环境的时区。由于本地环境的时区不是默认设置的,必须手动配置,Jackson将时区调整为GMT。

@Test
public void whenDeserialisingZonedDateTimeWithDefaults_thenNotCorrect()
  throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.findAndRegisterModules();
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Europe/Berlin"));
    String converted = objectMapper.writeValueAsString(now);

    ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class);
    System.out.println("serialized: " + now);
    System.out.println("restored: " + restored);
    assertThat(now, is(restored));
}

The test case will fail with output:

该测试用例将失败,输出。

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

Fortunately, there is a quick and simple fix for this odd default behavior; we just have to tell Jackson not to adjust the time zone.

幸运的是,对于这种奇怪的默认行为有一个快速而简单的解决方案;我们只需告诉杰克逊不要调整时区。

This can be done by adding the below line of code to the above test case:

这可以通过在上述测试案例中添加以下一行代码来实现。

objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

Note that, to preserve the time zone, we also have to disable the default behavior of serializing the date to the timestamp.

注意,为了保留时区,我们还必须禁用将日期序列化为时间戳的默认行为。

13. Custom Date Deserializer

13.自定义日期解串器

We can also use a custom Date deserializer. We’ll write a custom deserializer for the property “eventDate“:

我们也可以使用一个自定义的Date解序列器。我们将为属性”eventDate“编写一个自定义的解序列器。

public class CustomDateDeserializer extends StdDeserializer<Date> {

    private SimpleDateFormat formatter = 
      new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateDeserializer() {
        this(null);
    }

    public CustomDateDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Date deserialize(JsonParser jsonparser, DeserializationContext context)
      throws IOException, JsonProcessingException {
        String date = jsonparser.getText();
        try {
            return formatter.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

Next we’ll use it as the “eventDate” deserializer:

接下来我们将使用它作为”eventDate“的反序列化器。

public class Event {
    public String name;

    @JsonDeserialize(using = CustomDateDeserializer.class)
    public Date eventDate;
}

Finally, we’ll test it:

最后,我们将测试它。

@Test
public void whenDeserializingDateUsingCustomDeserializer_thenCorrect()
  throws JsonProcessingException, IOException {
 
    String json = "{"name":"party","eventDate":"20-12-2014 02:30:00"}";

    SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
    ObjectMapper mapper = new ObjectMapper();

    Event event = mapper.readerFor(Event.class).readValue(json);
    assertEquals("20-12-2014 02:30:00", df.format(event.eventDate));
}

14. Fixing InvalidDefinitionException

14.修复InvalidDefinitionException

When creating a LocalDate instance, we may come across an exception:

当创建一个LocalDate实例时,我们可能会遇到一个异常。

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance
of `java.time.LocalDate`(no Creators, like default construct, exist): no String-argument
constructor/factory method to deserialize from String value ('2014-12-20') at [Source:
(String)"2014-12-20"; line: 1, column: 1]

This problem occurs because JSON doesn’t natively have a date format, so it represents dates as String.

出现这个问题是因为JSON本身没有日期格式,所以它把日期表示为String

The String representation of a date isn’t the same as an object of type LocalDate in memory, so we need an external deserializer to read that field from a String, and a serializer to render the date to String format.

日期的String表示与内存中LocalDate类型的对象不一样,所以我们需要一个外部反序列化器来从String中读取该字段,并需要一个序列化器来将日期渲染成String格式。

These methods also apply to LocalDateTime, the only change is to use an equivalent class for LocalDateTime.

这些方法也适用于LocalDateTime,唯一的变化是为LocalDateTime使用一个等同的类。

14.1. Jackson Dependency

14.1.杰克逊的依赖性

Jackson allows us to fix this in a couple of ways. First, we have to make sure the jsr310 dependency is in our pom.xml:

Jackson允许我们通过几种方式来解决这个问题。首先,我们必须确保jsr310依赖性在我们的pom.xml中。

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

14.2. Serialization to Single Date Object

14.2.序列化为单一日期对象

In order to be able to handle LocalDate, we need to register the JavaTimeModule with our ObjectMapper.

为了能够处理LocalDate,我们需要将JavaTimeModule注册到我们的ObjectMapper

We also need to disable the feature WRITE_DATES_AS_TIMESTAMPS in ObjectMapper to prevent Jackson from adding time digits to the JSON output:

我们还需要在ObjectMapper中禁用WRITE_DATES_AS_TIMESTAMPS功能,以防止Jackson在JSON输出中加入时间数字。

@Test
public void whenSerializingJava8DateAndReadingValue_thenCorrect() throws IOException {
    String stringDate = "\"2014-12-20\"";

    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new JavaTimeModule());
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

    LocalDate result = mapper.readValue(stringDate, LocalDate.class);
    assertThat(result.toString(), containsString("2014-12-20"));
}

Here we used Jackson’s native support for serializing and deserializing dates.

这里我们使用Jackson的本地支持来序列化和反序列化日期。

14.3. Annotation in POJO

14.3.POJO中的注解

Another way to deal with the problem is to use the LocalDateDeserializer and JsonFormat annotations at the entity level:

另一种处理问题的方法是在实体层面使用LocalDateDeserializerJsonFormat注解。

public class EventWithLocalDate {

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

The @JsonDeserialize annotation is used to specify a custom deserializer to unmarshal the JSON object. Similarly, @JsonSerialize indicates a custom serializer to use when marshaling the entity.

@JsonDeserialize注解用于指定一个自定义的反序列化器来解开JSON对象的marshal。同样地,@JsonSerialize表示一个自定义的序列化器,以便在处理实体时使用。

In addition, the annotation @JsonFormat allows us to specify the format in which we will serialize date values. Therefore, this POJO can be used to read and write the JSON:

此外,注解@JsonFormat允许我们指定我们将序列化日期值的格式。因此,这个POJO可以用来读取和写入JSON。

@Test
public void whenSerializingJava8DateAndReadingFromEntity_thenCorrect() throws IOException {
    String json = "{\"name\":\"party\",\"eventDate\":\"20-12-2014\"}";

    ObjectMapper mapper = new ObjectMapper();

    EventWithLocalDate result = mapper.readValue(json, EventWithLocalDate.class);
    assertThat(result.getEventDate().toString(), containsString("2014-12-20"));
}

While this approach takes more work than using the JavaTimeModule defaults, it’s a lot more customizable.

虽然这种方法比使用JavaTimeModule的默认值需要更多的工作,但它的可定制性更强。

15. Conclusion

15.结论

In this extensive Date article, we examined several ways that Jackson can help marshal and unmarshal a date to JSON using a sensible format we have control over.

在这篇广泛的Date文章中,我们研究了Jackson可以使用我们可以控制的合理格式来帮助将日期编入和取消编入JSON的几种方法。

As always, the examples code can be found over on GitHub.

一如既往,示例代码可以在GitHub上找到