前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Jackson - LocalDateTime序列化探索

Jackson - LocalDateTime序列化探索

作者头像
夹胡碰
发布2021-06-17 19:51:19
2.8K0
发布2021-06-17 19:51:19
举报
文章被收录于专栏:程序猿~程序猿~

一、背景

在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
代码语言:javascript
复制
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
2. Jackson序列化与反序列化LocalDateTime
代码语言:javascript
复制
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]的序列化。
代码语言:javascript
复制
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]格式。
代码语言:javascript
复制
public class LocalDateTimeDeserializer
    extends JSR310DateTimeDeserializerBase<LocalDateTime>
{
    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序列化格式为数组。
代码语言:javascript
复制
public class LocalDateTimeSerializer extends JSR310FormattedSerializerBase<LocalDateTime>
{
    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格式进行序列化和反序列化,代码如下:
代码语言:javascript
复制
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")注解,代码如下:
代码语言:javascript
复制
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
代码语言:javascript
复制
Admin admin = mapper.readValue(str, Admin.class);
  1. ObjectMapper.readValue
代码语言:javascript
复制
public <T> T readValue(String content, Class<T> valueType)
        throws IOException, JsonParseException, JsonMappingException
    {
        return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueType));
    } 
  1. ObjectMapper._readMapAndClose
代码语言:javascript
复制
{
    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<Object> 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 执行反序列化
代码语言:javascript
复制
@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 获取序列化字段信息和反序列化器,并且执行反序列化
代码语言:javascript
复制
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
代码语言:javascript
复制
@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 最终进行对应类型的反序列化
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、背景
  • 二、Jackson使用及源码分析
    • 1. maven
      • 2. Jackson序列化与反序列化LocalDateTime
        • 3. 流程说明
          • 4. 源码解析
            • 5. 结论
              • 7. 详细源码分析
              相关产品与服务
              文件存储
              文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档