Jackson Date
最后更新:2024年5月11日
1. 概述
在本教程中,我们将使用 Jackson 序列化日期。我们将从序列化一个简单的 java.util.Date 开始,然后是 Joda-Time,最后是 Java 8 DateTime。
2. 将 Date 序列化为时间戳
首先,让我们看看如何使用 Jackson 序列化一个简单的 java.util.Date。
在下面的示例中,我们将序列化一个 “Event” 实例,该实例具有名为 “eventDate” 的 Date 字段
@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);
}
重要的是要注意,Jackson 默认会将 Date 序列化为时间戳格式(自 1970 年 1 月 1 日 UTC 以来的毫秒数)。
“event” 序列化的实际输出是
{
"name":"party",
"eventDate":3600000
}
3. 将 Date 序列化为 ISO-8601
序列化为这种简洁的时间戳格式并非最佳选择。相反,让我们将 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"));
}
我们可以看到,日期的表示现在更加易读。
4. 配置 ObjectMapper DateFormat
之前的解决方案仍然缺乏完全灵活性来选择准确的格式来表示 java.util.Date 实例。
相反,让我们来看一个配置,它将允许我们 设置表示日期的格式
@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));
}
请注意,即使我们现在在日期格式方面更加灵活,我们仍然在使用整个 ObjectMapper 级别的全局配置。
5. 使用 @JsonFormat 格式化 Date
接下来,让我们看看 @JsonFormat 注解,以 控制单个类上的日期格式, 而不是全局地应用于整个应用程序
public class Event {
public String name;
@JsonFormat
(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
public Date eventDate;
}
现在让我们测试一下
@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. 自定义 Date 序列化器
接下来,为了完全控制输出,我们将利用一个用于 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));
}
}
现在我们将它用作 “eventDate” 字段的序列化器
public class Event {
public String name;
@JsonSerialize(using = CustomDateSerializer.class)
public Date eventDate;
}
最后,我们将对其进行测试
@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. 使用 Jackson 序列化 Joda-Time
日期并不总是 java.util.Date 的实例。事实上,越来越多的日期由其他类表示,一个常见的类是 Joda-Time 库的 DateTime 实现。
让我们看看如何使用 Jackson 序列化 DateTime。
我们将使用 jackson-datatype-joda 模块,以获得开箱即用的 Joda-Time 支持
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
<version>2.17.2</version>
</dependency>
然后我们可以简单地注册 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. 使用自定义序列化器序列化 Joda DateTime
如果我们不想引入额外的 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));
}
}
然后我们可以将其用作我们的属性“eventDate”序列化器
public class Event {
public String name;
@JsonSerialize(using = CustomDateTimeSerializer.class)
public DateTime eventDate;
}
最后,我们可以将所有内容组合在一起并进行测试
@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. 使用 Jackson 序列化 Java 8 Date
现在我们来看看如何使用 Jackson 序列化 Java 8 DateTime,在这个例子中 LocalDateTime。 我们可以使用 jackson-datatype-jsr310 模块
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.2</version>
</dependency>
然后我们只需要注册 JavaTimeModule(JSR310Module 已弃用),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. 不使用任何额外依赖序列化 Java 8 Date
如果我们不想引入额外的依赖,我们总是可以使用 自定义序列化器将 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));
}
}
然后我们将使用该序列化器来处理我们的 “eventDate” 字段
public class Event {
public String name;
@JsonSerialize(using = CustomLocalDateTimeSerializer.class)
public LocalDateTime eventDate;
}
最后,我们将对其进行测试
@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. 反序列化 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. 以保留时区的方式反序列化 Joda ZonedDateTime
在默认配置下,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));
}
测试用例将输出失败
serialized: 2017-08-14T13:52:22.071+02:00[Europe/Berlin]
restored: 2017-08-14T11:52:22.071Z[UTC]
幸运的是,有一个快速而简单的解决方法来解决这种奇怪的默认行为; 我们只需要告诉 Jackson 不要调整时区。
可以通过将以下代码行添加到上面的测试用例中来实现
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
请注意,为了保留时区,我们还需要禁用将日期序列化为时间戳的默认行为。
13. 自定义 Date 反序列化器
我们也可以使用 自定义 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);
}
}
}
接下来我们将将其用作 “eventDate” 反序列化器
public class Event {
public String name;
@JsonDeserialize(using = CustomDateDeserializer.class)
public Date eventDate;
}
最后,我们将对其进行测试
@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. 修复 InvalidDefinitionException
在创建 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]
这个问题的原因是 JSON 本身并没有原生日期格式,所以它将日期表示为 String。
日期的 String 表示形式与内存中的 LocalDate 类型的对象并不相同,因此我们需要一个外部反序列化器来从 String 读取该字段,以及一个序列化器将日期渲染为 String 格式。
这些方法也适用于 LocalDateTime,唯一的改变是使用 LocalDateTime 的等效类。
14.1. Jackson 依赖
Jackson 允许我们通过几种方式解决这个问题。 首先,我们必须确保 jsr310 依赖 在我们的 pom.xml 中
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.2</version>
</dependency>
14.2. 序列化为单个日期对象
为了能够处理 LocalDate,我们需要将 JavaTimeModule 与我们的 ObjectMapper 注册。
我们还需要禁用 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"));
}
在这里,我们使用了 Jackson 原生支持来序列化和反序列化日期。
14.3. POJO 中的注解
解决问题的另一种方法是在实体级别使用 LocalDateDeserializer 和 JsonFormat 注解
public class EventWithLocalDate {
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
public LocalDate eventDate;
}
@JsonDeserialize 注解用于指定一个自定义反序列化器来取消编组 JSON 对象。 类似地,@JsonSerialize 指示在编组实体时要使用的自定义序列化器。
此外,@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"));
}
虽然这种方法比使用 JavaTimeModule 的默认设置需要更多的工作,但它具有更高的可定制性。
15. 结论
在这篇关于Date 的详细文章中,我们探讨了 Jackson 可以帮助将日期编组和反编组为 JSON 的几种方法,并且可以使用我们控制的合理格式。
支持本文的代码可在 GitHub 上获取。 一旦你以 Baeldung Pro 会员 身份登录,就开始学习并在项目上进行编码。















