淺談Gson和fastjson使用中的坑

程式猿劉川楓發表於2021-06-09

相信大家在程式碼編寫中都用過Gson和fastjson吧,用來進行 Java物件和json字串之間的轉換。

本篇文章就主要介紹博主在工作中使用這兩款工具時遇到的坑和對應的解決辦法。

覺得有用的可以點個贊哈~

1.前言

看了下我上一篇文章的釋出時間,已然是兩個月前了,這兩個月工作確實很忙,加班也不少,空閒的時間都去研究別的技術了(後面會寫文章),這次先用此篇文章記錄我這段時間工作中遇到的坑,其實寫文章的主要目的也是想記錄一下問題,便於幫助他人也便於自己以後檢視,廢話不多說,開始說明問題~

2.Gson和fastjson介紹

Google Gson是一個簡單的基於Java的庫,用於將Java物件序列化為JSON,反之亦然。 它是由Google開發的一個開源庫。

fastjson和Gson一樣的作用,但其是由國內阿里巴巴開發的一款工具。

fastjson比Gson快很多倍,也由於其是國內阿里開發,被大量使用,但也頻繁被爆出問題,官方也緊急修復更新版本了。但是實際使用中,這兩款工具也各有各的坑,合理的根據自己需要的去選擇工具即可。

3.問題說明及解決

下面將先說明我遇到的坑,然後會給出相應解決辦法,當然大家可以摸索更好的解決方式~

3.1 Gson會將Integer型別轉為Double

場景:一個業務需求的編輯功能,我需要把查詢後得到的id隱藏在頁面上,以便儲存時根據id去修改資料。但自測時發現傳到頁面上的值是double型別,但資料庫中是int,這就造成了無法修改的問題,一一排查問題後我發現是Gson搞的鬼

解決辦法1:使用fastjson即可解決,引入依賴後程式碼使用如下(推薦):

Map<String,Object> map = JSONObject.parseObject(result,Map.class);

解決方法2:將物件中的Integer型別改成String型別,這樣就不會被自動轉換了(根據自己業務情況使用)

解決方法3:在定義Gson時直接定義型別,程式碼如下:

private static Type typeToken = new TypeToken<TreeMap<String, Object>>(){}.getType();
Gson gson = new GsonBuilder()
            .registerTypeAdapter(
                    new TypeToken<TreeMap<String, Object>>(){}.getType(),
                    new JsonDeserializer<TreeMap<String, Object>>() {
                        @Override
                        public TreeMap<String, Object> deserialize(
                                JsonElement json, Type typeOfT,
                                JsonDeserializationContext context) throws JsonParseException {
 
                            TreeMap<String, Object> treeMap = new TreeMap<>();
                            JsonObject jsonObject = json.getAsJsonObject();
                            Set<Map.Entry<String, JsonElement>> entrySet = jsonObject.entrySet();
                            for (Map.Entry<String, JsonElement> entry : entrySet) {
                                Object ot = entry.getValue();
                                if(ot instanceof JsonPrimitive){
                                    treeMap.put(entry.getKey(), ((JsonPrimitive) ot).getAsString());
                                }else{
                                    treeMap.put(entry.getKey(), ot);
                                }
                            }
                            return treeMap;
                        }
                    }).create();

實際程式碼中呼叫:

Map<String, Object> params = gson.fromJson(result, typeToken);

3.2 Gson不序列化匿名內部類

場景:完成任務介面時寫了測試類來測介面,初始化Map集合的時候用瞭如下比較優雅的方式:

Map<String, String> map = new HashMap<String, String>() {
    {
	put("logNo", "123456");
        put("reqTime", "20210607");
    }
};
Gson gson = new Gson();
System.out.println(gson.toJson(map));

但是執行測試類輸出結果以後會發現為null。這是因為此種方式生成的Map是匿名內部類的例項,也就是new出來的map沒有類名。

查詢相關文章可知:內部類的介面卡會生成對外部類/例項的隱式引用,會導致迴圈引用。所以結論為:Gson不會序列化匿名內部類。

解決方法:使用fastjson就不會出現這種問題,可成功序列化轉為json資料

看到這裡是否覺得Gson出現的問題,fastjson都能解決,那下面就介紹下fastjson在實際應用中的坑。

3.3 fastjson會將資料順序改變

場景:在對接其他公司介面時往往都需要加密解密並簽名驗籤,為了防止傳輸過程中資料被篡改,主要是為了安全。此時json傳輸的資料順序則是重要的,如果被打亂則會解密驗籤失敗,我在用fastjson時就出現了這種問題,一度頭疼~

解決方法1:解析返回資料時增加引數不調整順序(推薦)

此時你的fastjson版本應該為1.2.3以上

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

然後在解析資料時增加 Feature.OrderedField 引數即可

FrontJsonResponse frontJsonResponse = JSONObject.parseObject(response,FrontJsonResponse.class, Feature.OrderedField);

解決方法2:使用Gson解析資料即可,不會改變資料順序

Gson gson = new Gson();
FrontJsonResponse frontJsonResponse = gson.fromJson(response,FrontJsonResponse.class)

3.4 fastjson會將欄位首字母變為小寫

場景:在對接其他公司介面時,文件中欄位名稱多為大寫字母(如:FILE_NM等)如果自己想定義物件實體來序列化的話,使用fastjson就會在上送時將實體中每個屬性的首字母變為小寫(如:fILE_NM),這樣上送則肯定會出問題,可以用以下方法解決:

解決方法1:在屬性名加上 @JSONField(name = "") 註解即可(推薦)

@JSONField(name = "FILE_NM")
private String FILE_NM;

解決方法2:序列化時增加引數 new PascalNameFilter() ,會將首字母強制轉換為大寫。

JSON.toJSONString(bean,new PascalNameFilter());

3.5 fastjson不會將多層json轉換為Map

場景:在對接第三方公司介面驗籤時,使用fastjson將返回資料轉換為Map,再去生成待簽名串驗籤時發現驗籤失敗,查詢後得知和第三方簽名是生成的串不一致,因為處理方式不同,具體場景請看下面示例:

    public static void main(String[] args) {
        String str = "{\"result\":{\"user_info\":{\"id\":14390753,\"sex\":1},\"complete_status\":0},\"errcode\":0}";
        Map<String,Object> fastMap = JSONObject.parseObject(str, Map.class);
        System.out.println("fastMap====="+fastMap);
        Gson gson = new Gson();
        Map<String,Object> gsonMap = gson.fromJson(str,Map.class);
        System.out.println("gsonMap====="+gsonMap);
    }

以上程式碼輸出結果為:

fastMap====={result={"user_info":{"sex":1,"id":14390753},"complete_status":0}, errcode=0}
gsonMap====={result={user_info={id=1.4390753E7, sex=1.0}, complete_status=0.0}, errcode=0.0}

對比處理方式和實際結果就可以知道,如果json字串裡有多層json物件,使用fastjson時並不會處理裡層的json,只會將外層json資料轉換為Map,而使用Gson時會將符合json格式的資料都轉換為Map。

最後跟第三方確認以後,因為他們不同介面有不同處理方式,我們這邊只能相容(因為他們改不了,怕影響別的使用方),根據不同介面去判斷該用fastjson還是用Gson解析返回的資料再進行驗籤(真的...一言難盡...)

所以遇到這種情況時,要確定對方是以什麼方式來處理,我們這邊再確定用什麼方式來接,這也是這兩種工具的一個不同點。

4.總結

實際開發過程中遇到以上問題就很頭疼,如果不一步一步debug看程式碼輸出結果,根本不會想到是json轉換工具的問題,但兩款工具確實各有各的優勢,我覺得選擇適合自己業務程式碼的就是最好的,這些問題也都能解決,也不是用不了,還是看解決方法而已,遇到的這些問題我都會記錄下來,等以後碰到類似問題了就能直接解決,這樣也不錯。

相關文章