Jackson - LocalDateTime序列化探索

一、背景

在Java开发中,涉及Json序列化及反序列化的情况有很多,最常见的就是SpringBoot/SpringCloud项目中HTTP/Rest接口的传参。其中经常会涉及到时间类型LocalDateTime的序列化和反序列化,这里经常会因为序列化失败,导致接外部接口调用或Feign调用失败。

先上结论:

Jackson默认使用jackson-datatype-jsr310JavaTimeModule进行序列化和反序列化配置。最终通过:
LocalDateTimeDeserializerdeserialize实现反序列化,默认支持yyyy-MM-ddTHH:mm:ss[yyyy, MM, dd, HH, mm, ss]格式。
LocalDateTimeSerializerserialize实现序列化,默认使用[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. 详细源码分析

以反序列化为例

  1. Main.readValue
Admin admin = mapper.readValue(str, Admin.class);
  1. ObjectMapper.readValue
public  T readValue(String content, Class valueType)
        throws IOException, JsonParseException, JsonMappingException
    {
        return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueType));
    } 
  1. 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 deser = _findRootDeserializer(ctxt, valueType);
            if (cfg.useRootWrapping()) {
                result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
            } else {
                // TODO: 执行反序列化
                result = deser.deserialize(p, ctxt);
            }
            ctxt.checkUnresolvedObjectId();
        }
        if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
            _verifyNoTrailingTokens(p, ctxt, valueType);
        }
        return result;
    }
}
 
 
  1. 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());
}
  1. 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;
    }
  1. 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);
    }
}
  1. LocalDateTimeDeserializer.deserialize
    最终进行对应类型的反序列化

你可能感兴趣的:(Jackson - LocalDateTime序列化探索)