一文詳解JackSon設定資訊

2022-06-24 15:01:08

背景

1.1 問題

Spring Boot 在處理物件的序列化和反序列化時,預設使用框架自帶的JackSon設定。使用框架預設的,通常會面臨如下問題:
  1. Date返回日期格式(建議不使用Date,但老專案要相容),帶有T,如 2018-05-15T24:59:59:
  1. LocalDate返回日期物件為陣列(框架中繼承了 WebMvcConfigurationSupport);
  1. LocalDateTime時間轉換失敗等;
  1. 定義了日期型別,如LocalDate,前端對接時(post/get),如果傳入日期字串("2022-05-05"),會報String 轉換為LocalDate失敗;
  1. 返回long型資料,前端js存在精度問題,需做轉換;
  1. 一些特殊物件要做業務特殊轉換,如加解密等;

1.2 解決方案

針對上述問題,存在很多種解決方案。由於底層框架統一設定攔截類實現的模式不同,還是會存在差異,本文主要說明在不同的設定場景下,自定義Jackson設定的一些注意事項和差異化原因:
為了解決特殊物件(如日期)的序列化和反序列化問題,常用方案如下:
  1. 針對特殊的具體物件,在物件上面使用註解,如:
@JsonSerialize(using= JsonDateSerializer.class)
private Date taskEndTime;

@ApiModelProperty(value = "檢查日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate checkDate;
  1. 重新實現WebMvcConfigurer介面,自定義JackSon設定。
  1. 繼承 WebMvcConfigurationSupport類,自定義JackSon設定。

1.3 特別說明

  • 方案1的模式,在對應變數上加上註解,是可以解決問題,但是嚴重編碼重複,不優雅;
  • 實現WebMvcConfigurer介面與繼承WebMvcConfigurationSupport類,是Spring Boot提供開發者做統全域性設定類的兩種模式,注意兩種模式的差異,詳情檢視後續章節介紹(兩種不同的模式,使用不當時,就會出現設定不生效的情況);

自定義Jackson

  1. JackSon設定說明

自定義一個Jackson設定資訊,需要了解Jackson的一些設定標準,如:
//在反序列化時忽略在 json 中存在但 Java 物件不存在的屬性

mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,

false);

//在序列化時日期格式預設為 yyyy-MM-dd'T'HH:mm:ss.SSSZ ,比如如果一個類中有private Date date;這種日期屬性,序列化後為:{"date" : 1413800730456},若不為true,則為{"date" : "2014-10-20T10:26:06.604+0000"}

mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);

//在序列化時忽略值為 null 的屬性

mapper.setSerializationInclusion(Include.NON_NULL);

//忽略值為預設值的屬性

mapper.setDefaultPropertyInclusion(Include.NON_DEFAULT);

// 美化輸出

mapper.enable(SerializationFeature.INDENT_OUTPUT);

// 允許序列化空的POJO類

// (否則會丟擲異常)

mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

// 把java.util.Date, Calendar輸出為數位(時間戳)

mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

// 在遇到未知屬性的時候不丟擲異常

mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

// 強制JSON 空字串("")轉換為null物件值:

mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);

// 在JSON中允許C/C++ 樣式的註釋(非標準,預設禁用)

mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);

// 允許沒有引號的欄位名(非標準)

mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);

// 允許單引號(非標準)

mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);

// 強制跳脫非ASCII字元

mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);

// 將內容包裹為一個JSON屬性,屬性名由@JsonRootName註解指定

mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);

//序列化列舉是以toString()來輸出,預設false,即預設以name()來輸出

mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true);

//序列化Map時對key進行排序操作,預設false

mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,true);

//序列化char[]時以json陣列輸出,預設false

mapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,true);

//序列化BigDecimal時之間輸出原始數位還是科學計數,預設false,即是否以toPlainString()科學計數方式來輸出

mapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,true);
View Code
  1. 實現WebMvcConfigurer介面

重新編寫一個ObjectMapper,替換系統預設的bean,就可以實現介面在post請求模式時,物件序列化與反序列化走子定義設定資訊了。
重新編寫Jackson後,並不能處理get請求時,日期等特殊物件的序列化處理;針對get請求,編寫物件的序列化規則函數,通過實現addFormatters()介面,可延伸支援;

編寫LocalDateTime轉換函數

/**
 * java 8 LocalDateTime轉換器
 *
 * @author wangling
 */
public class LocalDateTimeFormatter implements Formatter<LocalDateTime> {
    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public LocalDateTime parse(String text, Locale locale) throws ParseException {
        return LocalDateTime.parse(text, formatter);
    }

    @Override
    public String print(LocalDateTime object, Locale locale) {
        return formatter.format(object);
    }
}

編寫LocalDate轉換函數

/**
 * java 8 localDate轉換器
 *
 * @author wangling
 */
public class LocalDateFormatter implements Formatter<LocalDate> {
    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    @Override
    public LocalDate parse(String text, Locale locale) throws ParseException {
        return LocalDate.parse(text, formatter);
    }

    @Override
    public String print(LocalDate object, Locale locale) {
        return formatter.format(object);
    }
}

編寫Jackson設定

編寫一個自定義的ObjectMapper bean物件,設定優先順序替換預設bean。
/**
 * 專案全域性設定類
 * 
 * @author wangling
 * @date 2022/06/10
 */
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

  @Override
  public void addFormatters(FormatterRegistry registry) {
    registry.addFormatterForFieldType(LocalDate.class, new LocalDateFormatter());
    registry.addFormatterForFieldType(LocalDateTime.class, new LocalDateTimeFormatter());
  }

  @Bean
  @Primary
  public ObjectMapper ObjectMapper() {
    String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    String dateFormat = "yyyy-MM-dd";
    String timeFormat = "HH:mm:ss";
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    // 序列化
    javaTimeModule.addSerializer(
        LocalDateTime.class,
        new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
    javaTimeModule.addSerializer(
        LocalDate.class,
        new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat)));
    javaTimeModule.addSerializer(
        LocalTime.class,
        new LocalTimeSerializer(DateTimeFormatter.ofPattern(timeFormat)));
    javaTimeModule.addSerializer(
        Date.class,
        new DateSerializer(false, new SimpleDateFormat(dateTimeFormat)));

    // 反序列化
    javaTimeModule.addDeserializer(
        LocalDateTime.class,
        new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
    javaTimeModule.addDeserializer(
        LocalDate.class,
        new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
    javaTimeModule.addDeserializer(
        LocalTime.class,
        new LocalTimeDeserializer(DateTimeFormatter.ofPattern(timeFormat)));
    javaTimeModule.addDeserializer(Date.class, new DateDeserializers.DateDeserializer() {
      @SneakyThrows
      @Override
      public Date deserialize(JsonParser jsonParser, DeserializationContext dc) {
        String text = jsonParser.getText().trim();
        SimpleDateFormat sdf = new SimpleDateFormat(dateTimeFormat);
        return sdf.parse(text);
      }
    });
    javaTimeModule.addSerializer(Long.class, ToStringSerializer.instance);
    javaTimeModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
    objectMapper.registerModule(javaTimeModule);
    return objectMapper;
  }
}
  1. WebMvcConfigurationSupport類

編寫Jackson設定

重新編寫Jackson後,並不能處理get請求時,日期等特殊物件的序列化處理;針對get請求,編寫物件的序列化規則函數,通過實現addFormatters()介面,可延伸支援;
編寫自定義設定Jackson資訊時,需要重寫extendMessageConverters方法。具體技術細節原因,請參考檔案《Spring Boot實現WebMvcConfigurationSupport導致自定義的JSON時間返回格式不生效》
/**
 * 專案全域性設定類
 * 
 * @author wangling
 * @date 2022/06/10
 */
@Configuration
public class MvcInterceptorConfig extends WebMvcConfigurationSupport {

  @Override
  protected void addFormatters(FormatterRegistry registry) {
    // 用於get 全域性格式化日期轉換
    registry.addFormatterForFieldType(LocalDate.class, new LocalDateFormatter());
    registry.addFormatterForFieldType(LocalDateTime.class, new LocalDateTimeFormatter());
  }

  @Override
  protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    // 代替框架預設的JackSon設定 用於post 全域性格式化日期轉換,long轉字串
    MappingJackson2HttpMessageConverter jackson2HttpMessageConverter =
        new MappingJackson2HttpMessageConverter();
    jackson2HttpMessageConverter.setObjectMapper(ObjectMapper());
    // 基於順序,先執行自定義的
    converters.add(0, jackson2HttpMessageConverter);
  }


  private ObjectMapper ObjectMapper() {
    String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    String dateFormat = "yyyy-MM-dd";
    String timeFormat = "HH:mm:ss";
    ObjectMapper objectMapper = new ObjectMapper();
    //忽略空Bean轉json的錯誤
    objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    //忽略 在json字串中存在,但是在物件中不存在對應屬性的情況,防止錯誤。
    // 例如json資料中多出欄位,而物件中沒有此欄位。如果設定true,丟擲異常,因為欄位不對應;false則忽略多出的欄位,預設值為null,將其他欄位反序列化成功
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    JavaTimeModule javaTimeModule = new JavaTimeModule();
    // 序列化
    javaTimeModule.addSerializer(
        LocalDateTime.class,
        new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
    javaTimeModule.addSerializer(
        LocalDate.class,
        new LocalDateSerializer(DateTimeFormatter.ofPattern(dateFormat)));
    javaTimeModule.addSerializer(
        LocalTime.class,
        new LocalTimeSerializer(DateTimeFormatter.ofPattern(timeFormat)));
    javaTimeModule.addSerializer(
        Date.class,
        new DateSerializer(false, new SimpleDateFormat(dateTimeFormat)));

    // 反序列化
    javaTimeModule.addDeserializer(
        LocalDateTime.class,
        new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
    javaTimeModule.addDeserializer(
        LocalDate.class,
        new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
    javaTimeModule.addDeserializer(
        LocalTime.class,
        new LocalTimeDeserializer(DateTimeFormatter.ofPattern(timeFormat)));
    javaTimeModule.addDeserializer(Date.class, new DateDeserializers.DateDeserializer() {
      @SneakyThrows
      @Override
      public Date deserialize(JsonParser jsonParser, DeserializationContext dc) {
        String text = jsonParser.getText().trim();
        SimpleDateFormat sdf = new SimpleDateFormat(dateTimeFormat);
        return sdf.parse(text);
      }
    });
    javaTimeModule.addSerializer(Long.class, ToStringSerializer.instance);
    javaTimeModule.addSerializer(BigInteger.class, ToStringSerializer.instance);
    objectMapper.registerModule(javaTimeModule);
    return objectMapper;
  }
}
View Code

WebMvcConfigurer與WebMvcConfigurationSupport相關知識點

  1. 基礎知識點

Spring的 WebMvcConfigurer 介面提供了很多方法讓開發者來客製化SpringMVC的設定。
WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware。支援的自定義的設定更多更全,WebMvcConfigurerAdapter有的方法,這個類也都有。該類註釋內容翻譯:這是提供MVC Java config 背後設定的主要類。 通常是通過將@EnableWebMvc新增到應用程式的@Configuration類中來匯入的。 另一個更高階的選擇是直接從此類擴充套件並在需要時重寫方法,記住子類要新增@Configuration,重寫帶有@Bean的方法也要加上@Bean。
  1. 使用注意事項

          參考檔案:《攔截失效原因》
  1. 實現WebMvcConfigurer: 不會覆蓋WebMvcAutoConfiguration的設定
  1. 實現WebMvcConfigurer+註解@EnableWebMvc:會覆蓋WebMvcAutoConfiguration的設定
  1. 繼承WebMvcConfigurationSupport:會覆蓋WebMvcAutoConfiguration的設定
  1. 繼承DelegatingWebMvcConfiguration:會覆蓋WebMvcAutoConfiguration的設定
  1. 推薦使用模式

  1. 非必要,最好避免WebMvcConfigurer,WebMvcConfigurationSupport在一個專案中同時使用;
  1. 出於安全性攔截設定,建議專案採用WebMvcConfigurer介面的方式做全域性設定;
  1. 日期,時間等建議使用LocalDate,替換歷史的Date資料型別;