[SprigMVC/SpringBoot] JSON序列化专题之日期序列化问题:接口报Jackson框架错误“InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default:...”

发布时间 2023-05-31 23:20:52作者: 千千寰宇

1 问题描述

请求接口时,service层返回到controller层的数据结构为List<Map<Strig, Object>>,而Map中存在一个key=date,value type=java.time.LocalDateEntry,且日志报如下错误:

InvalidDefinitionException: Java 8 date/time type `java.time.LocalDate` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling (through reference chain: cn.xx.bd.dataservice.common.dto.CommonResponse["data"]->cn.xx.bd.dataservice.common.dto.page.v2.PageResponse["records"]->java.util.LinkedList[0]->java.util.HashMap["date"])”

2 问题分析

  • jackson默认不支持java8 LocalDate/LocalDateTime序列化反序列化,那控制台也显示了解决的办法(引入依赖com.fasterxml.jackson.datatype:jackson-datatype-jsr310,并启用对Map中"date"的entry的处理),只不过并不全。

因为 spring-mvc / spring-boot 是使用 jackson 作为 json 序列化反序列化工具的,故只需配置 jackson 即可

3 解决方法

解决方法1 : 类型转换(LocalDate/LocalDateTime --> Date)

将实体类中的LocalDate/LocalDateTime转为Date类型

解决方法2

Step1 引入依赖(com.fasterxml.jackson.datatype:jackson-datatype-jsr310)

<dependency>
	<groupId>com.fasterxml.jackson.datatype</groupId>
	<artifactId>jackson-datatype-jsr310</artifactId>
	<version>2.11.4</version>
</dependency>

spring-boot项目中直接引用:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-json</artifactId>
	<version>2.3.12.RELEASE</version>
</dependency>

Step2 策略1:配置Jackson的序列化/反序列化策略 # 为特定Class Bean的字段指定Jackson的序列化策略

通过注解指定

  • @JsonFormat / @DateTimeFormat / @JsonDeserialize / @JsonSerialize
    @TableField("update_time")
    @ApiModelProperty("更新时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") //此注解用来接收字符串类型的参数封装成LocalDateTime类型
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8", shape = JsonFormat.Shape.STRING) //此注解将date类型数据转成字符串响应出去
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)		// 反序列化
    @JsonSerialize(using = LocalDateTimeSerializer.class)		// 序列化
    private LocalDateTime updateTime;

    @TableField("create_time")
    @ApiModelProperty("添加时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") //此注解用来接收字符串类型的参数封装成LocalDateTime类型
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm",timezone = "GMT+8", shape = JsonFormat.Shape.STRING) //此注解将date类型数据转成字符串响应出去
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)		// 反序列化
    @JsonSerialize(using = LocalDateTimeSerializer.class)		// 序列化
    private LocalDateTime createTime;

Step2 策略2:配置Jackson的序列化/反序列化策略 # 全局配置(MvcConfiguration)

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());
        messageConverter.setObjectMapper(objectMapper);
        converters.add(0, messageConverter);
    }
}

或:

  • Step2.1 JacksonConfiguration
import cn.xx.bd.dataservice.biz.common.constants.Constants;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.springframework.context.annotation.Bean;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@Configuration
public class JacksonConfiguration {

    @Bean
    public ObjectMapper objectMapper(){
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
        JavaTimeModule javaTimeModule = new JavaTimeModule();

        javaTimeModule.addSerializer(LocalDateTime.class,new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_DATE_FORMAT)));
        javaTimeModule.addSerializer(LocalTime.class,new LocalTimeSerializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_TIME_FORMAT)));

        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_DATE_FORMAT)));
        javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(Constants.DateTime.DEFAULT_TIME_FORMAT)));
        objectMapper.registerModule(javaTimeModule).registerModule(new ParameterNamesModule());
        return objectMapper;
    }
}
  • Step2.2 Constants
public class Constants {
    public static class DateTime {
        /** 默认日期时间格式 */
        public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
        /** 默认日期格式 */
        public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
        /** 默认时间格式 */
        public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
    }
}

然后,只需要在实体类中对应的时间类型上使用 @DateTimeFormat@JsonFormat 即可。

解决方法3 以FastJson框架替换Jackson ObjectMapper

用阿里的FastJson替换ObjectMapper

X 参考文献