使用 fastjson 又又又翻車了,莫名其妙多了屬性。。

来源:https://www.cnblogs.com/javastack/archive/2023/07/24/17577398.html
-Advertisement-
Play Games

有一位同事說使用 fastjson 進行 JSON 序列化存儲到資料庫後,發現 JSON 字元串“莫名其妙地”多了一些屬性!幫看了下代碼,看到基本類型的布爾類型以 is 開頭的屬性,再看到 fastjson ,就有點想笑。 ## 復現 定義 MyClass ``` public class MyCl ...


有一位同事說使用 fastjson 進行 JSON 序列化存儲到資料庫後,發現 JSON 字元串“莫名其妙地”多了一些屬性!幫看了下代碼,看到基本類型的布爾類型以 is 開頭的屬性,再看到 fastjson ,就有點想笑。

復現

定義 MyClass

public class MyClass {

    // boolean 類型的屬性
    private boolean isActive;
    private boolean valid;

    // int 類型的屬性
    private int id;

    // 預設構造器
    public MyClass() {
    }

    // 帶有所有屬性的構造器
    public MyClass(boolean isActive, boolean valid, int id) {
        this.isActive = isActive;
        this.valid = valid;
        this.id = id;
    }

    // isActive 的 getter 和 setter 方法
    public boolean isActive() {
        return isActive;
    }

    public void setActive(boolean isActive) {
        this.isActive = isActive;
    }

    // valid 的 getter 和 setter 方法
    public boolean getValid() {
        return valid;
    }

    public void setValid(boolean valid) {
        this.valid = valid;
    }

    // id 的 getter 和 setter 方法
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

編寫測試代碼:

import com.alibaba.fastjson.JSON;

public class MyClassMain {
    public static void main(String[] args) {
        // 創建 MyClass 對象
        MyClass myClass = new MyClass(true, false, 123);

        // 使用 fastjson 序列化對象
        String jsonString = JSON.toJSONString(myClass);

        // 列印 JSON 字元串
        System.out.println(jsonString);
    }
}

結果:

{“active”:true,“id”:123,“valid”:false}

我們發現多了一個 active 屬性,少了一個 isActive 屬性!

推薦一個開源免費的 Spring Boot 實戰項目:

https://github.com/javastacks/spring-boot-best-practice

分析

通過調試可以發現,問題出現在下麵這個函數:

com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer(java.lang.Class<?>)

public final ObjectSerializer createJavaBeanSerializer(Class<?> clazz) {
    String className = clazz.getName();
    long hashCode64 = TypeUtils.fnv1a_64(className);
    if (Arrays.binarySearch(denyClasses, hashCode64) >= 0) {
        throw new JSONException("not support class : " + className);
    }

    // 關鍵
    SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy, fieldBased);
    if (beanInfo.fields.length == 0 && Iterable.class.isAssignableFrom(clazz)) {
        return MiscCodec.instance;
    }

    return createJavaBeanSerializer(beanInfo);
}

而 buildBeanInfo 的關鍵是com.alibaba.fastjson.util.TypeUtils#computeGetters

public static List<FieldInfo> computeGetters(Class<?> clazz, //
                                             JSONType jsonType, //
                                             Map<String,String> aliasMap, //
                                             Map<String,Field> fieldCacheMap, //
                                             boolean sorted, //
                                             PropertyNamingStrategy propertyNamingStrategy //
){
    // 省略部分代碼

        if(methodName.startsWith("is")){
            if(methodName.length() < 3){
                continue;
            }
            if(returnType != Boolean.TYPE
                    && returnType != Boolean.class){
                continue;
            }
            char c2 = methodName.charAt(2);
            String propertyName;
            Field field = null;
            if(Character.isUpperCase(c2)){
                if(compatibleWithJavaBean){
                    propertyName = decapitalize(methodName.substring(2));
                } else{
                    propertyName = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
                }
                // 這裡 isActive 的屬性名被計算出 active
                propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName, propertyName, 2);
            }

            // 省略其他

             JSONField fieldAnnotation = null;
            if(field != null){
                fieldAnnotation = TypeUtils.getAnnotation(field, JSONField.class);
                if(fieldAnnotation != null){
                    if(!fieldAnnotation.serialize()){
                        continue;
                    }
                    ordinal = fieldAnnotation.ordinal();
                    serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                    parserFeatures = Feature.of(fieldAnnotation.parseFeatures());
                    if(fieldAnnotation.name().length() != 0){
                        //關鍵: 使用 JSONField 註解設置的 name 替代屬性名
                        propertyName = fieldAnnotation.name();
                        if(aliasMap != null){
                            propertyName = aliasMap.get(propertyName);
                            if(propertyName == null){
                                continue;
                            }
                        }
                    }
                    if(fieldAnnotation.label().length() != 0){
                        label = fieldAnnotation.label();
                    }
                }
            }

            // 省略部分代碼

            FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                    annotation, fieldAnnotation, label);
            fieldInfoMap.put(propertyName, fieldInfo);
        }
    }
    Field[] fields = clazz.getFields();
    computeFields(clazz, aliasMap, propertyNamingStrategy, fieldInfoMap, fields);
    return getFieldInfos(clazz, sorted, fieldInfoMap);
}

其實 fastjson 通過反射雖然有能力識別真實的屬性名,但是實際操作時會根據 getter 方法反推出屬性名,造成轉為 JSON 字元串時和實際屬性名存在偏差。

解決辦法

遵循阿裡巴巴 Java 開發手冊

孤盡老師的《Java 開發手冊》 中專門強調任何布爾類型的變數都不要加 is 首碼,基本類型布爾屬性反向解析時,會誤以為不帶 is 導致獲取不到屬性,拋出異常。關註公眾號:Java核心技術,回覆:手冊,可獲取高清完整版。

使用別名

使用 fastjson 自帶的 @JSONField 註解,不過這種方式 fastjson 的侵入性太強。

public class MyClass {

    @JSONField( name="isActive")
    // boolean 類型的屬性
    private boolean isActive;
    private boolean valid;

    // 省略其他

}

總結

我認為 對於 Java 程式員而言,《阿裡巴巴 Java 開發手冊》至少讀 3 遍。 工作中發現太多常見低級問題都是 《阿裡巴巴 Java 開發手冊》已經存在的問題。關註公眾號:Java核心技術,回覆:手冊,可獲取高清完整版。

然而推薦很多次《阿裡巴巴 Java 開發手冊》雖然很薄,但是很多人還是不會認真閱讀幾遍,導致在相同的地方跌倒很多遍。哪怕遇到類似的問題,也很容易快速想出原因。

我們遇到問題時,一定不要止步於解決問題,而是應該尋找最合理的解決方案。比如雖然加上 @JSONField 可以“解決問題”,但侵入性太強,假如其他人也用這個對象使用其他 JSON 序列化工具,就會出問題,這並不是一個好的方案。

AI 時代,遇到問題自己如果不能快速解決時,可以考慮尋求 AI 的幫助。不過使用 AI 時一定要將問題交代清楚。很多同學說的問題連其他同事都聽不懂,更不別說 AI 了。

版權聲明:本文為CSDN博主「明明如月學長」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。原文鏈接:https://blog.csdn.net/w605283073/article/details/131270338

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2022最新版)

2.勁爆!Java 協程要來了。。。

3.Spring Boot 2.x 教程,太全了!

4.別再寫滿屏的爆爆爆炸類了,試試裝飾器模式,這才是優雅的方式!!

5.《Java開發手冊(嵩山版)》最新發佈,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 小王,你的頁面白屏了,趕快修複一下。小王排查後發現是服務端傳回來的數據格式不對導致,無數據時傳回來不是 [] 而是 null, 從而導致 forEach 方法報錯導致白屏,於是告訴測試,這是服務端的錯誤導致,要讓服務端來修改,結果測 ...
  • Docker 是什麼 先看看百科的定義: Docker 是一個開源的應用容器引擎,讓開發者可以打包他們的應用以及依賴包到一個可移植的鏡像中,然後發佈到任何流行的Linux或Windows操作系統的機器上,也可以實現虛擬化。容器是完全使用沙箱機制,相互之間不會有任何介面。 容器引擎?鏡像?容器?虛擬化 ...
  • 一、Array.from使用 通常Array都用於數組去重。下麵是Array的詳細用法: 1.將類似組轉化為真正的數組 函數參數轉化為數組 dom轉化為數組 這裡強調一下, 必須有length屬性,否則返回的是空數組。 索引必須是字元串數字,否則返回的是[undefined,undefined,un ...
  • 京銷易系統已經接入大網、KA以及雲倉三個條線商機,每個條線商機規則差異比較大,當前現狀是獨立實現三套系統分別做支撐。 ...
  • 本文主要講述了應對複雜性的一些原則和經驗,通過實際案例解構設計思想,個人認為好的設計是體現在「職責分離」、「抽象分層」和「變化擴展」上,在類的結構設計上尤其要花心思去想,如「變與不變分離」、「配置域與執行域分離」、「查詢與命令分離」。 ...
  • 容器安全是實施和管理像Docker這樣的容器技術的關鍵方面。它包括一組實踐、工具和技術,旨在保護容器化應用程式及其運行的基礎架構。在本節中,我們將討論一些關鍵的容器安全考慮因素、最佳實踐和建議。 ### **容器隔離** 隔離對於確保容器化環境的強大性和安全性至關重要。容器應該相互隔離,並與主機系統 ...
  • 一.pytesseract 1.簡介 Pytesseract是一個Python庫,用於將圖像中的文本轉換為可編輯的字元串。它是基於Google的Tesseract OCR引擎開發的 。Tesseract是一個開源的OCR引擎,能夠識別超過100種語言的文字。Pytesseract簡化了與Tesser ...
  • # Class類 ## **基本介紹** 1. Class也是類,因此也繼承Object類; 2. Class類對象不是new出來的,而是系統創建的; 3. 對於某個類的Class類對象,在記憶體中只有一份,因為類只載入一次; 4. 每個類的實例都會記得自己是由哪個Class實例所生成的; 5. 通過 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...