一、背景
在Java开发中,涉及Json
序列化及反序列化的情况有很多,最常见的就是SpringBoot/SpringCloud项目中HTTP/Rest接口的传参。其中经常会涉及到时间类型LocalDateTime
的序列化和反序列化,这里经常会因为序列化失败,导致接外部接口调用或Feign调用失败。
先上结论:
Jackson默认使用
jackson-datatype-jsr310
的JavaTimeModule
进行序列化和反序列化配置。最终通过:
LocalDateTimeDeserializer
的deserialize
实现反序列化,默认支持yyyy-MM-ddTHH:mm:ss
和[yyyy, MM, dd, HH, mm, ss]
格式。
LocalDateTimeSerializer
的serialize
实现序列化,默认使用[yyyy, MM, dd, HH, mm, ss]
格式。
下面将对Jackson的序列化及反序列化进行样例测试及源码分析。
二、Jackson使用及源码分析
1. maven
com.fasterxml.jackson.core
jackson-databind
2.9.0
com.fasterxml.jackson.core
jackson-annotations
2.9.0
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
2. Jackson序列化与反序列化LocalDateTime
String str = "{\"id\":\"666\",\"createTime\":[2014, 10, 10, 10, 10, 10]}";
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
Admin admin = mapper.readValue(str, Admin.class); // TODO: 反序列化
System.out.println("POJO-toString: " + admin.toString());
// TODO: out => POJO-toString: JacksonTest.Admin(id=666, createTime=2014-10-10T10:10:10, updateTime=null)
String adminStr = mapper.writeValueAsString(admin); // TODO: 序列化
System.out.println("POJO-toJsonString: " + adminStr);
// TODO: out => POJO-toJsonString: {"id":"666","createTime":[2014,10,10,10,10,10],"updateTime":null}
3. 流程说明
4. 源码解析
添加jackson-datatype-jsr310
,并且执行mapper.findAndRegisterModules();
自动注册modules
,即可添加对LocalDateTime
的序列化及反序列化。
实际注册的是com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
,通过打断点和追源码可以确认。
- JavaTimeModule源码分析
可以看到额外添加了LocalDateTime
的序列化和反序列化。
LocalDateDeserializer.INSTANCE
支持yyyy-MM-ddTHH:mm:ss
和[yyyy, MM, dd, HH, mm, ss]
格式的反序列化。
LocalDateTimeSerializer.INSTANCE
支持[yyyy, MM, dd, HH, mm, ss]
的序列化。
public final class JavaTimeModule extends SimpleModule
{
...
public JavaTimeModule()
{
...
// TODO: 反序列化
addDeserializer(LocalDateTime.class, LocalDateTimeDeserializer.INSTANCE);
addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE);
addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE);
...
// TODO: 序列化
addSerializer(LocalDateTime.class, LocalDateTimeSerializer.INSTANCE);
addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE);
addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE);
...
}
- LocalDateTimeDeserializer.INSTANCE源码分析
可以看到LocalDateDeserializer.INSTANCE
支持了yyyy-MM-ddTHH:mm:ss
格式的支持,并且deserialize
方法支持了两种LocalDateTime
的反序列化格式,
一是通过LocalDateTime.parse(string, DEFAULT_FORMATTER);
解析yyyy-MM-ddTHH:mm:ss
格式。
二是通过LocalDateTime.of(year, month, day, hour, minute);
解析[yyyy, MM, dd, HH, mm, ss]
格式。
public class LocalDateTimeDeserializer
extends JSR310DateTimeDeserializerBase
{
private static final long serialVersionUID = 1L;
private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
public static final LocalDateTimeDeserializer INSTANCE = new LocalDateTimeDeserializer();
private LocalDateTimeDeserializer() {
this(DEFAULT_FORMATTER);
}
public static final DateTimeFormatter ISO_LOCAL_DATE_TIME;
static {
ISO_LOCAL_DATE_TIME = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(ISO_LOCAL_DATE)
.appendLiteral('T')
.append(ISO_LOCAL_TIME)
.toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE);
}
...
@Override
public LocalDateTime deserialize(JsonParser parser, DeserializationContext context) throws IOException
{
// TODO: 支持yyyy-MM-ddTHH:mm:ss的格式
if (parser.hasTokenId(JsonTokenId.ID_STRING)) {
...
if (_formatter == DEFAULT_FORMATTER) {
// JavaScript by default includes time and zone in JSON serialized Dates (UTC/ISO instant format).
if (string.length() > 10 && string.charAt(10) == 'T') {
if (string.endsWith("Z")) {
return LocalDateTime.ofInstant(Instant.parse(string), ZoneOffset.UTC);
} else {
return LocalDateTime.parse(string, DEFAULT_FORMATTER);
}
}
}
return LocalDateTime.parse(string, _formatter);
} catch (DateTimeException e) {
return _handleDateTimeException(context, e, string);
}
}
// TODO: 支持[yyyy, MM, dd, HH, mm, ss]的格式
if (parser.isExpectedStartArrayToken()) {
...
if (t == JsonToken.VALUE_NUMBER_INT) {
LocalDateTime result;
int year = parser.getIntValue();
int month = parser.nextIntValue(-1);
int day = parser.nextIntValue(-1);
int hour = parser.nextIntValue(-1);
int minute = parser.nextIntValue(-1);
t = parser.nextToken();
if (t == JsonToken.END_ARRAY) {
result = LocalDateTime.of(year, month, day, hour, minute);
} else {
...
}
- LocalDateTimeSerializer.INSTANCE源码分析
LocalDateTime
序列化格式为数组。
public class LocalDateTimeSerializer extends JSR310FormattedSerializerBase
{
public static final LocalDateTimeSerializer INSTANCE = new LocalDateTimeSerializer();
protected LocalDateTimeSerializer() {
// TODO: 这里DateTimeFormatter为null
this(null);
}
public LocalDateTimeSerializer(DateTimeFormatter f) {
super(LocalDateTime.class, f);
}
@Override
public void serialize(LocalDateTime value, JsonGenerator g, SerializerProvider provider)
throws IOException
{
// TODO: 序列化为数组
if (useTimestamp(provider)) {
g.writeStartArray();
_serializeAsArrayContents(value, g, provider);
g.writeEndArray();
} else {
DateTimeFormatter dtf = _formatter;
if (dtf == null) {
dtf = _defaultFormatter();
}
g.writeString(value.format(dtf));
}
}
5. 结论
采用默认的
JavaTimeModule
进行LocalDateTime
的序列化与反序列化,反序列化支持yyyy-MM-ddTHH:mm:ss
和[yyyy, MM, dd, HH, mm, ss]
的格式,序列化默认为[yyyy, MM, dd, HH, mm, ss]
的格式。
6. 解决办法
- 方法1
手动添加序列化格式,即可对yyyy-MM-dd HH:mm:ss
格式进行序列化和反序列化,代码如下:
public static void main(String[] args) throws IOException {
String str = "{\"id\":\"666\",\"createTime\":\"2014-10-10 10:10:10\"}";
ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
javaTimeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
mapper.registerModule(javaTimeModule);
Admin admin = mapper.readValue(str, Admin.class);
System.out.println("POJO-toString: " + admin.toString());
// TODO: out => POJO-toString: JustTest.Admin(id=666, createTime=2014-10-10T10:10:10)
String adminStr = mapper.writeValueAsString(admin);
System.out.println("POJO-toJsonString: " + adminStr);
// TODO: out => POJO-toJsonString: {"id":"666","createTime":"2014-10-10 10:10:10"}
}
- 方法2
实体类添加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
注解,代码如下:
public class JustTest {
public static void main(String[] args) throws IOException {
String str = "{\"id\":\"666\",\"createTime\":\"2014-10-10 10:10:10\"}";
ObjectMapper mapper = new ObjectMapper();
mapper.findAndRegisterModules();
Admin admin = mapper.readValue(str, Admin.class);
System.out.println("POJO-toString: " + admin.toString());
// TODO: out => POJO-toString: JustTest.Admin(id=666, createTime=2014-10-10T10:10:10)
String adminStr = mapper.writeValueAsString(admin);
System.out.println("POJO-toJsonString: " + adminStr);
// TODO: out => POJO-toJsonString: {"id":"666","createTime":"2014-10-10 10:10:10"}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Admin{
private String id;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
}
}
7. 详细源码分析
以反序列化为例
- Main.readValue
Admin admin = mapper.readValue(str, Admin.class);
- ObjectMapper.readValue
public T readValue(String content, Class valueType)
throws IOException, JsonParseException, JsonMappingException
{
return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueType));
}
- ObjectMapper._readMapAndClose
{
try (JsonParser p = p0) {
Object result;
JsonToken t = _initForReading(p, valueType);
final DeserializationConfig cfg = getDeserializationConfig();
final DeserializationContext ctxt = createDeserializationContext(p, cfg);
if (t == JsonToken.VALUE_NULL) {
// Ask JsonDeserializer what 'null value' to use:
result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = null;
} else {
JsonDeserializer
- BeanDeserializer.deserialize
执行反序列化
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
{
// common case first
if (p.isExpectedStartObjectToken()) {
if (_vanillaProcessing) {
// TODO: 执行反序列化
return vanillaDeserialize(p, ctxt, p.nextToken());
}
// 23-Sep-2015, tatu: This is wrong at some many levels, but for now... it is
// what it is, including "expected behavior".
p.nextToken();
if (_objectIdReader != null) {
return deserializeWithObjectId(p, ctxt);
}
return deserializeFromObject(p, ctxt);
}
return _deserializeOther(p, ctxt, p.getCurrentToken());
}
- BeanDeserializer.vanillaDeserialize
获取序列化字段信息和反序列化器,并且执行反序列化
private final Object vanillaDeserialize(JsonParser p,
DeserializationContext ctxt, JsonToken t)
throws IOException
{
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom serializers
p.setCurrentValue(bean);
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.getCurrentName();
do {
p.nextToken();
// TODO: 获取BeanProperty,里面包含要反序列化的字段信息以及
// 对应的反序列化器,内部通过BeanPropertyMap.init()方法在执行
// 反序列化时对Object[] _hashArea进行缓存,然后在此处获取
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
// TODO: 执行反序列化
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
}
return bean;
}
- MethodProperty.deserializeAndSet
@Override
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
Object instance) throws IOException
{
Object value;
if (p.hasToken(JsonToken.VALUE_NULL)) {
if (_skipNulls) {
return;
}
value = _nullProvider.getNullValue(ctxt);
} else if (_valueTypeDeserializer == null) {
// TODO: 反序列化器执行反序列化
value = _valueDeserializer.deserialize(p, ctxt);
} else {
value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer);
}
try {
_setter.invoke(instance, value);
} catch (Exception e) {
_throwAsIOE(p, e, value);
}
}
- LocalDateTimeDeserializer.deserialize
最终进行对应类型的反序列化