SpringMVC Json自定義序列化和反序列化

需求背景

需求一:SpringMVC構建的微服務系統,數據庫對日期的存儲是Long類型的時間戳,前端之前是默認使用Long類型時間,現在前端框架改動,要求後端響應數據時,Long類型的時間自動變成標準時間格式(yyyy-MM-dd HH:mm:ss)。

涉及到這個轉換的範圍挺大,所有的實體表都有創建時間createTime和修改時間updateTime,目前的主要訴求也是針對這兩個字段,並且在實體詳情數據和列表數據都存在,需要一個統一的方法,對這兩個字段進行處理。

需求二:前端請求上傳的JSON報文,String類型的內容,可能會出現前後有空格的現象,如果前端框架未對此問題進行處理,後端收到的JSON請求反序列化爲對象時,就會出現String類型的值,前後有空格,現需要一個統一的處理方法,對接收的String類型屬性執行trim方法。

解決方案

SpringMVC默認的JSON框架爲jackson,也可以使用fastjson。

jackson框架

自定義序列化

如果項目使用jackson框架做json序列化,推薦的方案是使用@JsonSerialize註解,示例代碼如下:

@JsonSerialize(using = CustomDateSerializer.class)  
private Long createTime;

@JsonSerialize(using = CustomDateSerializer.class)  
private Long updateTime;

CustomDateSerializer類的實現示例如下:

public class CustomDateSerializer extends JsonSerializer<Long> {

	@Override
	public void serialize(Long aLong, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		Date date = new Date(aLong);
		jsonGenerator.writeString(sdf.format(date));
	}
}

這種方案的好處如下:

  1. 自定義的實現類可以複用
  2. 精準到需要轉換處理的字段,不受限於createTime和updateTime,更貼近於需求

缺點就是需要轉換的字段都需要使用註解,工作量有點大

當然有其他的統一處理方案,這裏不贅述。

自定義反序列化

在jackson框架上實現自定義序列化,也是非常方便的,繼承SimpleModule類即可:

@Component
public class StringTrimModule extends SimpleModule {

    public StringTrimModule() {
        addDeserializer(String.class, new StdScalarDeserializer<String>(String.class) {
            @Override
            public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
                String value = jsonParser.getValueAsString();
                if (StringUtils.isEmpty(value)) {
                     return value;
                }
                return value.trim();
            }
        });
    }
}

fastjson框架

如果工程裏出現這個依賴:

<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>fastjson</artifactId>
	<version>1.2.62</version>
</dependency>

說明此工程使用的json框架爲fastjson,那麼jackson的@JsonSerialize就不會有觸發入口了,我們來看看fastjson的處理方式。

自定義序列化

相應的,使用fastjson會有相應的配置類,示例如下:

/**
 * 統一輸出是採用fastJson
 *
 * @return
 */
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
	//convert轉換消息的對象
	FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

	//處理中文亂碼問題
	List<MediaType> fastMediaTypes = new ArrayList<>();
	fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
	fastConverter.setSupportedMediaTypes(fastMediaTypes);

	//是否要格式化返回的json數據
	FastJsonConfig fastJsonConfig = new FastJsonConfig();
	fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
	// 添加指定字段的值轉換處理
    fastJsonConfig.setSerializeFilters(new CustomerDateFilter());
	// FastJson禁用autoTypeSupport
	fastJsonConfig.getParserConfig().setAutoTypeSupport(false);
	fastConverter.setFastJsonConfig(fastJsonConfig);

	return new HttpMessageConverters(fastConverter);
}

這裏需要添加fastjson對字段值的處理(上述代碼已添加這行代碼),如

// 添加指定字段的值轉換處理
fastJsonConfig.setSerializeFilters(new CustomerDateFilter());

CustomerDateFilter爲自行實現的類,代碼如下:

public class CustomerDateFilter implements ValueFilter {

	@Override
	public Object process(Object object, String name, Object value) {
		if (FieldConstants.CREATE_TIME.equalsIgnoreCase(name) ||  FieldConstants.UPDATE_TIME.equalsIgnoreCase(name)) {
			// 屬性名爲createTime, updateTime進行轉換處理
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
			sdf.setTimeZone(TimeZone.getTimeZone("GMT+8"));

			if(value instanceof Long) {
				Long time = (Long) value;
				Date date = new Date(time);
				return sdf.format(date);
			} else {
				return value;
			}
		}
		return value;
	}
}

這樣就可以把所有響應對象中出現的createTime和updateTime字段統一處理了,無論列表數據還是單個對象數據,非常方便。缺點就是除此之外的字段,如果還做不到全系統統一,就需要單獨處理。

SerializeFilter定製序列化

支持SerializeFilter定製序列化的擴展編程接口有以下幾個,可根據實際需要進行擴展:

  • PropertyPreFilter: 根據PropertyName判斷是否序列化;
  • PropertyFilter: 根據PropertyName和PropertyValue來判斷是否序列化;
  • NameFilter: 修改Key,如果需要修改Key,process返回值則可;
  • ValueFilter: 修改Value;
  • BeforeFilter: 序列化時在最前添加內容;
  • AfterFilter: 序列化時在最後添加內容;
自定義反序列化

fastJson提供了序列化過濾器,來實現自定義序列化改造,但沒有提供反序列化過濾器,來實現對應的功能。

方案:@JSONField註解

回到對JSON報文String類型的值執行trim操作,官網支持@JSONField註解的屬性設置(要求fastJson版本1.2.36以上):

@JSONField(format="trim")
private String name;

在JSON報文反序列化時,該實體的name屬性會自動執行trim方法進行處理。

此方案只有逐個添加註解,工作量較大。

方案:實現ObjectDeserializer接口

ObjectDeserializer接口爲可以實現自定義反序列化實現接口,配合ParserConfig的全局設置,也可以達到預期的效果,合建StringTrimDeserializer類,對String進行處理:

/**
 * @title: StringTrimDeserializer
 * @description: 把String類型的內容統一做trim操作
 */
public class StringTrimDeserializer implements ObjectDeserializer {

	@Override
	public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
		// JSON String反序列化的邏輯比較複雜,在StringCodec的基礎上,對其結果調用trim方法
		Object obj = StringCodec.instance.deserialze(parser, type, fieldName);
		if (obj instanceof String) {
			String str = (String) obj;
			return (T) str.trim();
		}
		return (T) obj;
	}

	@Override
	public int getFastMatchToken() {
		return JSONToken.LITERAL_STRING;
	}
}

相應在,在HttpMessageConverters類fastJsonHttpMessageConverters方法內中增加String類的反序列化設置:

// 設置String類的全局反序列化規則:自動完成trim操作
ParserConfig.getGlobalInstance().putDeserializer(String.class, new StringTrimDeserializer());

tips:
在StringTrimDeserializer類實現方法中爲什麼不直接parser.getLexer().stringVal()得到值後執行trim方法,而是調用StringCodec.instance的實現方法?

StringCodec是fastJson默認的String類型的反序列化邏輯類,裏面要處理的類型有String、StringBuffer、StringBuilder等,還有各種的集合、數組結構,涉及的nextToken值都不相同,總之,對String文本的反序列化,實現邏輯和應對的場景都比較複雜,而此次的需求只是對String執行trim操作,複雜的邏輯還是交給StringCodec來處理,站在StringCodec的基礎上,對其結果執行trim方法就可以達到預期目標。

小結

今天這篇是記錄Json自定義序列化和反序列化的實踐方案,開始實施前先確認工程裏使用的框架是哪個,否則就會出現添加了@JsonSerialize註解,搞了大半天沒有效果,回頭一看框架是fastjson,沒有觸發入口,當然得不到預期效果,小小建議,希望對你有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章