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

需求背景

需求一:SpringMVC構(gòu)建的微服務(wù)系統(tǒng),數(shù)據(jù)庫(kù)對(duì)日期的存儲(chǔ)是Long類型的時(shí)間戳,前端之前是默認(rèn)使用Long類型時(shí)間,現(xiàn)在前端框架改動(dòng),要求后端響應(yīng)數(shù)據(jù)時(shí),Long類型的時(shí)間自動(dòng)變成標(biāo)準(zhǔn)時(shí)間格式(yyyy-MM-dd HH:mm:ss)。

涉及到這個(gè)轉(zhuǎn)換的范圍挺大,所有的實(shí)體表都有創(chuàng)建時(shí)間createTime和修改時(shí)間updateTime,目前的主要訴求也是針對(duì)這兩個(gè)字段,并且在實(shí)體詳情數(shù)據(jù)和列表數(shù)據(jù)都存在,需要一個(gè)統(tǒng)一的方法,對(duì)這兩個(gè)字段進(jìn)行處理。

需求二:前端請(qǐng)求上傳的JSON報(bào)文,String類型的內(nèi)容,可能會(huì)出現(xiàn)前后有空格的現(xiàn)象,如果前端框架未對(duì)此問(wèn)題進(jìn)行處理,后端收到的JSON請(qǐng)求反序列化為對(duì)象時(shí),就會(huì)出現(xiàn)String類型的值,前后有空格,現(xiàn)需要一個(gè)統(tǒng)一的處理方法,對(duì)接收的String類型屬性執(zhí)行trim方法。

解決方案

SpringMVC默認(rèn)的JSON框架為jackson,也可以使用fastjson。

jackson框架

自定義序列化

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

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

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

CustomDateSerializer類的實(shí)現(xiàn)示例如下:

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. 自定義的實(shí)現(xiàn)類可以復(fù)用
  2. 精準(zhǔn)到需要轉(zhuǎn)換處理的字段,不受限于createTime和updateTime,更貼近于需求

缺點(diǎn)就是需要轉(zhuǎn)換的字段都需要使用注解,工作量有點(diǎn)大

當(dāng)然有其他的統(tǒng)一處理方案,這里不贅述。

自定義反序列化

在jackson框架上實(shí)現(xiàn)自定義序列化,也是非常方便的,繼承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框架

如果工程里出現(xiàn)這個(gè)依賴:

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

說(shuō)明此工程使用的json框架為fastjson,那么jackson的@JsonSerialize就不會(huì)有觸發(fā)入口了,我們來(lái)看看fastjson的處理方式。

自定義序列化

相應(yīng)的,使用fastjson會(huì)有相應(yīng)的配置類,示例如下:

/**
 * 統(tǒng)一輸出是采用fastJson
 *
 * @return
 */
@Bean
public HttpMessageConverters fastJsonHttpMessageConverters() {
    //convert轉(zhuǎn)換消息的對(duì)象
    FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();

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

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

    return new HttpMessageConverters(fastConverter);
}

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

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

CustomerDateFilter為自行實(shí)現(xiàn)的類,代碼如下:

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進(jìn)行轉(zhuǎn)換處理
            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;
    }
}

這樣就可以把所有響應(yīng)對(duì)象中出現(xiàn)的createTime和updateTime字段統(tǒng)一處理了,無(wú)論列表數(shù)據(jù)還是單個(gè)對(duì)象數(shù)據(jù),非常方便。缺點(diǎn)就是除此之外的字段,如果還做不到全系統(tǒng)統(tǒng)一,就需要單獨(dú)處理。

SerializeFilter定制序列化

支持SerializeFilter定制序列化的擴(kuò)展編程接口有以下幾個(gè),可根據(jù)實(shí)際需要進(jìn)行擴(kuò)展:

  • PropertyPreFilter: 根據(jù)PropertyName判斷是否序列化;
  • PropertyFilter: 根據(jù)PropertyName和PropertyValue來(lái)判斷是否序列化;
  • NameFilter: 修改Key,如果需要修改Key,process返回值則可;
  • ValueFilter: 修改Value;
  • BeforeFilter: 序列化時(shí)在最前添加內(nèi)容;
  • AfterFilter: 序列化時(shí)在最后添加內(nèi)容;
自定義反序列化

fastJson提供了序列化過(guò)濾器,來(lái)實(shí)現(xiàn)自定義序列化改造,但沒(méi)有提供反序列化過(guò)濾器,來(lái)實(shí)現(xiàn)對(duì)應(yīng)的功能。

方案:@JSONField注解

回到對(duì)JSON報(bào)文String類型的值執(zhí)行trim操作,官網(wǎng)支持@JSONField注解的屬性設(shè)置(要求fastJson版本1.2.36以上):

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

在JSON報(bào)文反序列化時(shí),該實(shí)體的name屬性會(huì)自動(dòng)執(zhí)行trim方法進(jìn)行處理。

此方案只有逐個(gè)添加注解,工作量較大。

方案:實(shí)現(xiàn)ObjectDeserializer接口

ObjectDeserializer接口為可以實(shí)現(xiàn)自定義反序列化實(shí)現(xiàn)接口,配合ParserConfig的全局設(shè)置,也可以達(dá)到預(yù)期的效果,合建StringTrimDeserializer類,對(duì)String進(jìn)行處理:

/**
 * @title: StringTrimDeserializer
 * @description: 把String類型的內(nèi)容統(tǒng)一做trim操作
 */
public class StringTrimDeserializer implements ObjectDeserializer {

    @Override
    public <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
        // JSON String反序列化的邏輯比較復(fù)雜,在StringCodec的基礎(chǔ)上,對(duì)其結(jié)果調(diào)用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;
    }
}

相應(yīng)在,在HttpMessageConverters類fastJsonHttpMessageConverters方法內(nèi)中增加String類的反序列化設(shè)置:

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

tips:
在StringTrimDeserializer類實(shí)現(xiàn)方法中為什么不直接parser.getLexer().stringVal()得到值后執(zhí)行trim方法,而是調(diào)用StringCodec.instance的實(shí)現(xiàn)方法?

StringCodec是fastJson默認(rèn)的String類型的反序列化邏輯類,里面要處理的類型有String、StringBuffer、StringBuilder等,還有各種的集合、數(shù)組結(jié)構(gòu),涉及的nextToken值都不相同,總之,對(duì)String文本的反序列化,實(shí)現(xiàn)邏輯和應(yīng)對(duì)的場(chǎng)景都比較復(fù)雜,而此次的需求只是對(duì)String執(zhí)行trim操作,復(fù)雜的邏輯還是交給StringCodec來(lái)處理,站在StringCodec的基礎(chǔ)上,對(duì)其結(jié)果執(zhí)行trim方法就可以達(dá)到預(yù)期目標(biāo)。

小結(jié)

今天這篇是記錄Json自定義序列化和反序列化的實(shí)踐方案,開(kāi)始實(shí)施前先確認(rèn)工程里使用的框架是哪個(gè),否則就會(huì)出現(xiàn)添加了@JsonSerialize注解,搞了大半天沒(méi)有效果,回頭一看框架是fastjson,沒(méi)有觸發(fā)入口,當(dāng)然得不到預(yù)期效果,小小建議,希望對(duì)你有幫助。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容