1. 概述

在这个教程中,我们将探讨如何使用 Jackson 序列化 OffsetDateTime 类型。OffsetDateTime 是 ISO-8601 日历系统中与格林威治标准时间(UTC)有偏移的日期时间的不可变表示。例如,2023-10-31T01:30+01:00 表示 2023 年 10 月 31 日的最后一分钟,与 UTC 有一个小时的时间差。

Jackson 默认情况下不支持序列化 OffsetDateTime,因为它是 Java 8 的日期时间类型。让我们看看如何启用它。

2. 项目设置

2.1. 依赖项

首先,我们需要在 pom.xml 中添加 Jackson 数据绑定 依赖:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.1</version>
</dependency>

2.2. 代码示例

首先,定义一个包含 OffsetDateTime 类型字段的类,并尝试将其序列化为 JSON:

public class User {
    private OffsetDateTime createdAt;

    // constructors, getters and setters
}

接下来,创建一个方法来将 User 对象序列化为 JSON:

String serializeUser() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    User user = new User();
    user.setCreatedAt(OffsetDateTime.parse("2021-09-30T15:30:00+01:00"));

    return objectMapper.writeValueAsString(user);
}

如果调用上述方法,我们会遇到以下错误:

Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.OffsetDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: com.baeldung.offsetdatetime.User["createdAt"])

如我们所见,Jackson 默认情况下不支持序列化 OffsetDateTime。让我们来看看如何解决这个问题。

3. 注册 JavaTimeModule

正如错误消息所示,Jackson 提供了一个名为 JavaTimeModule 的模块,我们可以使用它来以正确的格式序列化 OffsetDateTime

首先,我们需要在 pom.xml 中添加 [jackson-datatype-jsr310](https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310) 依赖:

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

现在,我们可以在序列化对象之前使用 ObjectMapper 注册这个模块:

String serializeUser() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    objectMapper.registerModule(new JavaTimeModule());
    
    User user = new User();
    user.setCreatedAt(OffsetDateTime.parse("2021-09-30T15:30:00+01:00"));

    return objectMapper.writeValueAsString(user);
}

我们使用 registerModule() 方法注册了 JavaTimeModule。同时,我们禁用了 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS 特性,以便得到与输入格式相同的日期,而不是作为时间戳。

当再次调用方法时,错误消失,输出中我们会看到序列化的日期。我们可以通过JUnit测试验证这一点:

@Test
void givenUser_whenSerialized_thenCreatedDateIsSerialized() throws JsonProcessingException {
    Assertions.assertEquals("{\"createdAt\":\"2021-09-30T15:30:00+01:00\"}", Main.serializeUser());
}

4. 定制序列化和反序列化

另一种解决方法是为 OffsetDateTime 创建一个自定义序列化器。如果我们还想定制日期的格式,这将是首选的方法。

4.1. 自定义序列化器

我们可以通过创建一个继承自 Jackson 的 JsonSerializer 类并重写 serialize() 方法来实现这一点:

public class OffsetDateTimeSerializer extends JsonSerializer<OffsetDateTime> {
    private static final DateTimeFormatter DATE_TIME_FORMATTER
      = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss XXX");
    
    @Override
    public void serialize(OffsetDateTime value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) 
      throws IOException {
        if (value == null) {
            throw new IOException("OffsetDateTime argument is null.");
        }
        jsonGenerator.writeString(DATE_TIME_FORMATTER.format(value));
    }
}

关于上面的代码,重要的是:

  • 我们创建了一个 DateTimeFormatter,用于指定我们想要使用的格式。这里我们使用了与默认不同的格式。
  • 接下来,我们调用 DateTimeFormatterformat() 方法格式化日期。
  • 最后,通过调用 JsonGeneratorwriteString() 方法将格式化的日期写入。

现在,我们可以在序列化对象之前使用 ObjectMapper 注册这个序列化器:

String customSerialize() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new SimpleModule().addSerializer(OffsetDateTime.class, new OffsetDateTimeSerializer()));
    
    User user = new User();
    user.setCreatedAt(OffsetDateTime.parse("2021-09-30T15:30:00+01:00"));
    
    return objectMapper.writeValueAsString(user);
}

现在我们可以确认输出中的日期符合序列化器指定的格式:

@Test
void givenUser_whenCustomSerialized_thenCreatedDateIsSerialized() throws JsonProcessingException {
    Assertions.assertEquals("{\"createdAt\":\"30-09-2021 15:30:00 +01:00\"}", Main.customSerialize());
}

4.2. 自定义反序列化器

由于我们已经创建了自定义序列化器,我们也需要创建一个 自定义反序列化器 来从 JSON 字符串反序列化日期。如果不这样做,我们还会收到相同的 InvalidDefinitionException

我们可以通过创建一个继承自 JsonDeserializer 的类并重写 deserialize() 方法来实现这一点:

public class OffsetDateTimeDeserializer extends JsonDeserializer<OffsetDateTime> {
    private static final DateTimeFormatter DATE_TIME_FORMATTER
      = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss XXX");
    
    @Override
    public OffsetDateTime deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) 
      throws IOException {
        String dateAsString = jsonParser.getText();
        if (dateAsString == null) {
            throw new IOException("OffsetDateTime argument is null.");
        }
        return OffsetDateTime.parse(dateAsString, DATE_TIME_FORMATTER);
    }
}

同样地,我们创建了一个 DateTimeFormatter,并将其格式传递给 parse() 方法,以便获取 OffsetDateTime 对象并返回值。

我们可以在需要反序列化对象的地方使用 ObjectMapper 注册这个反序列化器:

String customDeserialize() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(new SimpleModule().addDeserializer(OffsetDateTime.class, new OffsetDateTimeDeserializer()));
    
    String json = "{\"createdAt\":\"30-09-2021 15:30:00 +01:00\"}";
    User user = objectMapper.readValue(json, User.class);
    
    return returnedUser.getCreatedAt().toString();
}

输出中,我们得到的是默认的 OffsetDateTime 格式的日期:

@Test
void givenUser_whenCustomDeserialized_thenCreatedDateIsDeserialized() throws JsonProcessingException {
    Assertions.assertEquals("2021-09-30T15:30+01:00", Main.customDeserialize());
}

5. 总结

在这篇文章中,我们了解了如何使用 Jackson 序列化和反序列化 OffsetDateTime。我们介绍了两种解决方案来处理 OffsetDateTime 的默认序列化问题:一是使用 JavaTimeModule,二是定义自定义序列化器。

如往常一样,本文的代码示例可以在 GitHub 上找到。