美文网首页
Jackson - LocalDateTime序列化探索

Jackson - LocalDateTime序列化探索

作者: 夹胡碰 | 来源:发表于2021-06-10 10:52 被阅读0次

    一、背景

    在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
    <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
    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<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序列化格式为数组。
    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格式进行序列化和反序列化,代码如下:
    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> T readValue(String content, Class<T> 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<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
      执行反序列化
    @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序列化探索

          本文链接:https://www.haomeiwen.com/subject/sasjeltx.html