1. 概述
本文将介绍如何使用 Jackson 序列化和反序列化日期时间对象。我们会从 java.util.Date
开始,然后是 Joda-Time,最后是 Java 8 的 DateTime
。
目标是帮助你更好地控制日期在 JSON 中的格式,避免踩坑,同时提供多种灵活的解决方案。
2. 将 Date
序列化为时间戳
默认情况下,Jackson 会将 java.util.Date
序列化为时间戳(毫秒数),例如:
{
"name": "party",
"eventDate": 3600000
}
这是一个简单示例:
@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);
}
✅ 优点:简单粗暴
❌ 缺点:可读性差
3. 将 Date
序列化为 ISO-8601 格式
为了提升可读性,可以将日期格式化为 ISO-8601:
{
"name": "party",
"eventDate": "1970-01-01T02:30:00.000+00:00"
}
实现方式如下:
@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);
mapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
String result = mapper.writeValueAsString(event);
assertThat(result, containsString("1970-01-01T02:30:00.000+00:00"));
}
⚠️ 注意:StdDateFormat
是 Jackson 2.9+ 的 ISO-8601 实现
4. 配置 ObjectMapper
的 DateFormat
如果希望统一控制日期格式,可以通过 ObjectMapper.setDateFormat()
设置全局格式:
@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));
}
⚠️ 注意:这是全局配置,影响所有日期字段
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
序列化器
如需完全控制序列化输出,可以编写自定义序列化器:
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 {
gen.writeString(formatter.format(value));
}
}
然后在字段上使用:
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. 序列化 Joda-Time 的 DateTime
使用 Jackson 自带的 jackson-datatype-joda
模块可以轻松支持 Joda-Time:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
<version>2.9.7</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
序列化器
不使用 jackson-datatype-joda
依赖时,也可以自定义序列化器:
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 {
gen.writeString(formatter.print(value));
}
}
使用方式:
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. 序列化 Java 8 的 LocalDateTime
推荐使用 jackson-datatype-jsr310
模块支持 Java 8 的时间 API:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.7</version>
</dependency>
注册 JavaTimeModule
:
@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 时间序列化
也可以自定义 LocalDateTime
序列化器:
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 {
gen.writeString(formatter.format(value));
}
}
字段使用:
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 也可以反序列化 JSON 字符串为 Date
对象:
@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 会将 ZonedDateTime
调整为本地时区,如需保留原始时区,需禁用如下配置:
objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
13. 自定义 Date
反序列化器
编写自定义反序列化器如下:
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 {
String date = jsonparser.getText();
try {
return formatter.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
字段使用:
public class Event {
public String name;
@JsonDeserialize(using = CustomDateDeserializer.class)
public Date eventDate;
}
14. 修复 InvalidDefinitionException
异常
14.1 添加 Jackson JSR310 依赖
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.11.0</version>
</dependency>
14.2 注册 JavaTimeModule 并禁用时间戳
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
14.3 使用 @JsonFormat
+ 自定义序列化器
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
public LocalDate eventDate;
15. 总结
本文总结了 Jackson 在处理日期时间序列化和反序列化方面的多种方式:
- 默认时间戳格式
- 使用 ISO-8601 提升可读性
- 全局
ObjectMapper
配置 - 字段级别
@JsonFormat
- 自定义序列化器/反序列化器
- 支持 Joda-Time 和 Java 8 的
DateTime
根据项目需求选择合适方案即可。完整代码示例可在 GitHub 找到。