1. 引言
在处理JSON数据时,统一日期格式对保持数据清晰性和兼容性至关重要。本教程将探讨使用Jackson的ObjectMapper在序列化时格式化Instant字段,并在反序列化时解析回原始对象的多种技术方案。我们还将讨论@JsonFormat注解的使用,以及扩展现有序列化器/反序列化器来实现完全控制。
2. 场景与准备
为演示这些技术,我们设定一个基础场景:使用预定义的日期格式和DateTimeFormatter:
public interface Instants {
ZoneOffset TIMEZONE = ZoneOffset.UTC;
String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS";
DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT)
.withZone(ZoneOffset.UTC);
}
为简化处理,我们使用UTC时区。核心目标是验证当使用ObjectMapper时,能否将Instant字段序列化为指定格式,并反序列化回原始Instant对象。 因此我们在测试中加入示例日期:
class InstantFormatUnitTest {
final String DATE_TEXT = "2024-05-27 12:34:56.789";
final Instant DATE = Instant.from(Instants.FORMATTER.parse(DATE_TEXT));
// ...
}
测试基础方案包括验证两点:序列化后JSON字符串是否包含预期日期格式,以及反序列化后的timeStamp字段是否匹配原始DATE对象。 我们通过以下断言方法实现:
void assertSerializedInstantMatchesWhenDeserialized(TimeStampTracker object, ObjectMapper mapper)
throws JsonProcessingException {
String json = mapper.writeValueAsString(object);
assertTrue(json.contains(DATE_TEXT));
TimeStampTracker deserialized = mapper.readValue(json, object.getClass());
assertEquals(DATE, deserialized.getTimeStamp());
}
由于需要测试不同方案,我们定义简单接口:
public interface TimeStampTracker {
Instant getTimeStamp();
}
3. 通过自定义JsonSerializer实现完全控制
先看最标准通用的方案:扩展JsonSerializer来控制非标准字段的序列化。该类是泛型的,可用于控制任意字段的序列化。我们为Instant类型编写实现:
public class CustomInstantSerializer extends JsonSerializer<Instant> {
@Override
public void serialize(Instant instant, JsonGenerator json, SerializerProvider provider)
throws IOException {
// ...
}
}
重写serialize()时,核心参数是JsonGenerator,我们用它按指定格式写入instant值:
json.writeString(Instants.FORMATTER.format(instant));
序列化搞定后,确保能反序列化特定格式的对象。
3.1. 自定义JsonDeserializer
反序列化采用类似方案,扩展JsonDeserializer:
public class CustomInstantDeserializer extends JsonDeserializer<Instant> {
@Override
public Instant deserialize(JsonParser json, DeserializationContext context)
throws IOException {
// ...
}
}
重写deserialize()时,我们获得JSON解析器而非生成器。调用json.getText()获取字段值,传给formatter解析:
return Instant.from(Instants.FORMATTER.parse(json.getText()));
3.2. 使用自定义序列化器/反序列化器
使用自定义实现需要结合@JsonSerialize和@JsonDeserialize注解。 传入我们的实现类:
public class Event implements TimeStampTracker {
@JsonSerialize(using = CustomInstantSerializer.class)
@JsonDeserialize(using = CustomInstantDeserializer.class)
private Instant timeStamp;
// standard getters and setters
}
测试验证:生成的JSON包含期望的日期格式,反序列化后的instant字段匹配原始日期:
@Test
void givenDefaultMapper_whenUsingCustomSerializerDeserializer_thenExpectedInstantFormat()
throws JsonProcessingException {
Event object = new Event();
object.setTimeStamp(DATE);
ObjectMapper mapper = new ObjectMapper();
assertSerializedInstantMatchesWhenDeserialized(object, mapper);
}
✅ 适用场景:仅少数类需要处理Instant字段,或特定类需要特殊序列化/反序列化逻辑时。
4. 添加JavaTimeModule扩展
Jackson默认不支持Instant类型,需在pom.xml添加依赖:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>1.17.1</version>
</dependency>
缺少此依赖时,序列化包含Instant字段的类会报错:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Java 8 date/time type `java.time.Instant` not supported by default
该依赖包含JavaTimeModule类,后续会用到。
4.1. 使用@JsonFormat指定格式
默认情况下,ObjectMapper将日期字段序列化为数字时间戳。在JsonMapper.builder()中调用disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)可禁用此行为,但无法指定具体格式。 因为defaultDateFormat()仅对Date和long有效。此时可用@JsonFormat注解:
public class Session implements TimeStampTracker {
@JsonFormat(pattern = Instants.DATE_FORMAT, timezone = "UTC")
private Instant timeStamp;
// standard getters and setters
}
⚠️ 必须设置timezone属性。这里复用"UTC"时区和开头定义的DATE_FORMAT。
4.2. 测试解决方案
整合测试序列化与反序列化:
@Test
void givenTimeModuleMapper_whenJsonFormat_thenExpectedInstantFormat()
throws JsonProcessingException {
Session object = new Session();
object.setTimeStamp(DATE);
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();
assertSerializedInstantMatchesWhenDeserialized(object, mapper);
}
由于使用了@JsonFormat,无需禁用WRITE_DATES_AS_TIMESTAMPS。
5. 扩展InstantSerializer实现自定义格式
Jackson自带多数类型的序列化器,包括InstantSerializer,其单例可通过JavaTimeModule使用:
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(Instant.class, InstantSerializer.INSTANCE);
❌ 但此方案无法自定义格式。由于InstantSerializer无公共构造函数,我们扩展它:
public class GlobalInstantSerializer extends InstantSerializer {
public GlobalInstantSerializer() {
super(InstantSerializer.INSTANCE, false, false, Instants.FORMATTER);
}
}
使用接收单例和formatter的构造函数。传入false给useTimestamp和useNanoseconds,因为我们希望Instant字段使用特定格式。此时类中无需任何注解:
public class History implements TimeStampTracker {
private Instant timeStamp;
// standard getters and setters
}
5.1. 扩展InstantDeserializer实现自定义格式
反序列化时,扩展InstantDeserializer并用InstantDeserializer.INSTANT常量和我们的formatter构造:
public class GlobalInstantDeserializer extends InstantDeserializer<Instant> {
public GlobalInstantDeserializer() {
super(InstantDeserializer.INSTANT, Instants.FORMATTER);
}
}
注意:与序列化器不同,反序列化器是泛型的,可接受任意Temporal类型作为反序列化返回类型。
5.2. 使用自定义InstantSerializer/InstantDeserializer
最后配置Java Time Module使用我们的实现并测试:
@Test
void givenTimeModuleMapper_whenSerializingAndDeserializing_thenExpectedInstantFormat()
throws JsonProcessingException {
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(Instant.class, new GlobalInstantSerializer());
module.addDeserializer(Instant.class, new GlobalInstantDeserializer());
History object = new History();
object.setTimeStamp(DATE);
ObjectMapper mapper = JsonMapper.builder()
.addModule(module)
.build();
assertSerializedInstantMatchesWhenDeserialized(object, mapper);
}
✅ 此方案最高效灵活:无需类注解,且适用于所有含Instant字段的类。
6. 总结
本文通过扩展Jackson内置序列化器/反序列化器,深入理解了自定义实现机制。我们结合@JsonFormat注解和扩展模块,实现了Instant字段的统一格式化。这不仅提升了JSON数据的可读性和兼容性,更在应用各层提供了日期时间表示的灵活控制力。
完整源码请见GitHub仓库。