Java經常使用json序列化工具的部分侷限性

Problem

在寫Java的Unit Test時,對於Unit Test所指望的值,通常是利用工具從test/resources目錄下將expectResult.json讀取進來並用json序列化工具進行反序列化來得到Unit Test所指望的結果,並與測試的實際結果進行對比。然而如果反序列化所用的類來源於第三方庫(即不能更改任何代碼),會遇到很大的問題。如下以本人對阿里雲的StreamRecord類進行反序列化時遇到的問題進行描述:java

StreamRecord類的定義以下:git

public class StreamRecord {

    public enum RecordType {
        /**
         * PUT類型
         * 若是對應行已存在,該Record須要覆蓋原有數據。
         */
        PUT,

        /**
         * UPDATE類型
         * 若是對應行已存在,該Record是在原有數據上的更新。
         */
        UPDATE,

        /**
         * DELETE類型
         * 代表要刪除對應的行。
         */
        DELETE
    }

    /**
     * Record的類型
     */
    private RecordType recordType;

    /**
     * 對應行的主鍵
     */
    private PrimaryKey primaryKey;

    /**
     * 對應行的時序信息
     */
    private RecordSequenceInfo sequenceInfo;

    /**
     * 該Record包含的屬性列,爲RecordColumn類型
     */
    private List<RecordColumn> columns;

    /**
     * 獲取Record的類型
     * @return Record的類型
     */
    public RecordType getRecordType() {
        return recordType;
    }

    public void setRecordType(RecordType recordType) {
        this.recordType = recordType;
    }

    /**
     * 獲取對應行的主鍵
     * @return 對應行的主鍵
     */
    public PrimaryKey getPrimaryKey() {
        return primaryKey;
    }

    public void setPrimaryKey(PrimaryKey primaryKey) {
        this.primaryKey = primaryKey;
    }

    /**
     * 獲取該行的時序信息
     * @return 該行的時序信息
     */
    public RecordSequenceInfo getSequenceInfo() {
        return sequenceInfo;
    }
    public void setSequenceInfo(RecordSequenceInfo sequenceInfo) {
        this.sequenceInfo = sequenceInfo;
    }

    /**
     * 獲取該Record包含的屬性列列表
     * @return 該Record包含的屬性列列表
     */
    public List<RecordColumn> getColumns() {
        if (columns != null) {
            return columns;
        } else {
            return new ArrayList<RecordColumn>();
        }
    }

    public void setColumns(List<RecordColumn> columns) {
        this.columns = columns;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[RecordType:]");
        sb.append(this.recordType);
        sb.append("\n[RecordSequenceInfo:]");
        sb.append(this.sequenceInfo);
        sb.append("\n[PrimaryKey:]");
        sb.append(this.primaryKey);
        sb.append("\n[Columns:]");
        for (RecordColumn column : this.getColumns()) {
            sb.append("(");
            sb.append(column);
            sb.append(")");
        }
        return sb.toString();
    }

}

本工程原先只用Jackson進行序列化和反序列化,但Jackson的ObjectMapper在對此類進行反序列化時,報了No suitable constructor的錯誤,通過調查發現Jackson進行反序列化須要默認的構造函數(若是有帶參數的構造函數,還要用@JsonCreator修飾構造函數,用@JsonProperty修飾構造函數參數),而上述類沒有,即便有咱們也不能對阿里雲等第三方庫進行更改,遂放棄Jackson,轉而考慮阿里本身的fastjson。fastjson的確能對該類進行反序列化,可是當我仔細分析反序列化後的對象時,發現有些深層的字段的值爲null,又通過一番調查,瞭解到fastjson雖然對反序列化的類沒有構造函數的要求,但對字段有要求,反序列化的private字段要有setter方法才能正常的反序列化(或者有一個帶有全部字段參數的構造函數),如果private字段缺乏setter方法,則該字段的值爲默認值。最後考慮用Google的Gson,Gson沒有上述這些問題,可是若反序列化類有Object類型的字段,而該字段的值爲數值型,則Gson都會轉爲Double型,好比你有個字段爲github

private Map<String, Object> map;

json文件:編程

{
  "age": 24,
  "height": 1.81
}

當把上述json文件反序列化爲map字段時,直覺上會認爲「age」字段的值的類型應該爲Integer或Long型,然而Gson這裏有點反常,因爲map的value爲Object類型,並未明確指定具體的數值類型,它會將key爲「age」的字段會變爲Double類型(並非咱們直覺上所指望的Integer或Long型),給後續編程帶來麻煩。關於Gson的這個「特性」,能夠參考https://github.com/google/gso... 上面的「debate」,比較有趣的「網友懟做者」。json

我最終的解決方案是用Gson反序列化,再利用反射工具ReflectionTestUtils.setField來對某些數值進行Double到Long的轉化。app

Conclusion

  • Jackson 功能強大,但對反序列化的類的要求較高(要有默認的constructor)。
  • Fastjson 速度快,可是對反序列化的類也有必定要求,並且在反序列化複雜的json時bug較多(阿里雲的不少sdk也因爲這個緣由並未使用fastjson)。
  • Gson 比較全面,對反序列化的類的要求最低,可是對於Object類型的數值字段處理不夠友好。

以上實驗所用版本:ide

compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
compile group: 'com.alibaba', name: 'fastjson', version: '1.2.56'

所以,若是沒有遇到序列化和反序列化第三方庫的model的狀況下(即代碼沒法更改的狀況),首選Jackson,不然選Gson。函數

相關文章
相關標籤/搜索