【FastJSON】解決FastJson中“$ref 迴圈引用”的問題

OkidoGreen發表於2020-04-05

https://blog.csdn.net/qq_35873847/article/details/78850528

【需求與環境描述】

0、開發環境

  SSH,EasyUI,MySQL 

1、需求要求:

  (1)首先獲取所有的貸款訂單資料,即List <LoanOrder>。

  (2)然後從單個貸款訂單實體LoanOrder去訪問貸款人實體Loaner的資訊。 

2、實體之間的關係描述

  (1)LoanOrder實體與Loaner實體是雙向的多對一和一對多關係。

  (2)LoanOrder是“多方”,其中的關係屬性為“private Loaner loaner”。

  (3)Loaner是“一方”,其中的關係屬性為“Set<LoanOrder> orders”。 

3、程式碼示例

  (1) 貸款訂單LoanOrder程式碼

1

2

3

4

5

6

7

8

9

10

11

12

13

@Entity

@Table(name = "t_bp_loan_order")

public class LoanOrder implements java.io.Serializable {

     

    /*省略其他次要屬性*/

    private Loaner loaner;

 

    @ManyToOne(fetch = FetchType.LAZY)

    @JoinColumn(name = "LOANER_ID")

    public Loaner getLoaner() {

        return this.loaner;

    }

}

  (2) Loaner方程式碼

1

2

3

4

5

6

7

8

9

10

11

12

13

@Entity

@Table(name = "t_bp_loaner")

public class Loaner implements java.io.Serializable {

 

    /* 其他普通屬性略去 */

    private Set<LoanOrder> loanOrders = new HashSet<LoanOrder>(0);

 

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "loaner")

    @JSONField(serialize = true)

    public Set<LoanOrder> getLoanOrders() {

        return this.loanOrders;

    }

}

 

【障礙再現】

1、需求01:

首先獲取所有的貸款訂單資料,即List <LoanOrder>,發現貸款人“張三”有兩個訂單。

 

2、需求02:

  然後,依次在第一個和第二個貸款訂單中點選“張三”,從而去訪問“名字叫做‘張三’”貸款人實體Loaner的資訊。

結果,第一個訂單可以顯示貸款人的資料,但是在第二個訂單資料中,不能獲取到“loaner(貸款人)”的資料,並且loaner中提示“$ref”。

經過兩次點選後,伺服器後臺傳送到前臺頁面上的JSON資料如下所示:

 

【解決方案】

第一步:禁用FastJson的“迴圈引用檢測”特性。

1、核心程式碼

 

2、作用

決定了生成的“多個”JSON物件中,是否載入被引用的同一個物件的資料。

在此,決定了生成的“多個”貸款訂單JSON物件中,是否載入被引用的同一個貸款人JSON物件的資料。

 

3、開啟和關閉FastJson的“迴圈引用檢測”特性的對比

 

當從伺服器端傳來的多個LoanOrder物件通過FASTJSON被序列化到“前端”後,會被瀏覽器解析成“DOM”物件。 

(1) 當開啟FastJson的“迴圈引用檢測”特性時:

1)對於第一個LoanOrder 01,fastjson會完全解析並載入它的所有資料,包括它所關聯的Loaner貸款人資訊,如下圖所示。

 

2) 對於第二個LoanOrder 02,fastjson僅僅解析並載入其貸款訂單部分的資料,對於“$ref”所指向的 Loaner貸款人的資料,fastjson會因為“開啟了fastJson的‘迴圈引用檢測’機制”而不去載入該貸款人資料。

當載入第二個貸款訂單資料時,fastjson檢測到已經在第一個訂單LoanOrder 01中載入了“貸款人Loaner”的資料,fastjson會因為“開啟了‘迴圈引用檢測’機制”而不去再次載入該貸款人資料,而僅僅將一個指向第一個貸款訂單LoanOrder01中“貸款人”的引用賦值給第二個貸款訂單中的貸款人的位置。

因此,在生成的第二個貸款訂單的JSON串中,對於貸款人資訊,僅僅只有一個“$ref”。

而jQuery這個前端技術又無法解析該引用,因此,就無法讀取貸款人的資料,如下圖所示。

 

第二步:禁止Loaner物件獲取Set<LoanOrder>的資料。

方法一:將原來的“雙向關係”修改為“單向關係”

1、原來:LoanOrder與Loaner之間是雙向關係。

2、修改後:只能從LoanOrder訪問Loaner,從Loaner無法訪問到LoanOrder。

3、具體方法01

       重要前提:不刪除Loaner中的“Set<LoanOrder> orders”屬性。

  注意,若在採用註解對映實體類的方式中,沒有使用“@Transient”註解,則資料庫會報錯。

4、具體方法02

       直接刪除“Set<LoanOrder> orders”屬性極其相關的setter()和getter()方法。

      

方法二:不修改關係的前提下,禁止序列化

在不修改LoanOrder和Loaner雙向關係的情況下,Loaner物件中的Set<LoanOrder>集合完成資料的載入,當其向前端Browser傳遞JSON資料時,禁止序列化Set<LoanOrder>集合。

具體方法:

設定註解“@JSONField(serialize = false)”。

說明:

A.“@JSONField”是fastjson提供的註解標籤,其作用為控制其所標註的屬性“能否被序列化”。

B.在此其作用為:禁止"loanOrders"這個Set集合被序列化。

具體如下圖所示。

 

 

【解決後的效果】 

 

讀者如要轉載,請標明出處和作者名,謝謝。
地址01:http://space.itpub.net/25851087
地址02:http://www.cnblogs.com/zjrodger
作者名:zjrodger  

 

 

 

問題描述:

今天在做後臺傳資料到前端解析的時候遇到了這個問題。背景介紹下:後臺傳過去json資料是用阿里的fastjson轉換的,呼叫的是這個方法[java] view plain copy

  1. String s = JSON.toJSONStringWithDateFormat(o,dateformat,SerializerFeature.WriteDateUseDateFormat);  
  2. ServletActionContext.getResponse().getWriter().write(s);  

其中dataformat是格式化時間資料的。傳過去的是一個PageBean物件,該物件繼承了Page物件,Page裡面包含list,pageNo,pageSize。前臺在通過data.list準備取出list迴圈做點事情的時候,發現全是undefined,通過console.log(data.list),發現這個資料竟然是這個東西$ref: "$.list[0]"。百度搜了下,這裡就是迴圈引用造成的。

問題分析:

迴圈引用:當一個物件包含另一個物件時,fastjson就會把該物件解析成引用。引用是通過$ref標示的,下面介紹一些引用的描述

  • "$ref":".." 上一級
  • "$ref":"@" 當前物件,也就是自引用
  • "$ref":"$" 根物件
  • "$ref":"$.children.0" 基於路徑的引用,相當於 root.getChildren().get(0)

解決方案:

fastjson提供了多種json轉換方案,有興趣的同學可以自己看看原始碼,這裡我們可以採用禁止迴圈引用的方案:

[java] view plain copy

  1. String s = JSON.toJSONStringWithDateFormat(0,"yyyy-MM-dd HH:mm:ss",SerializerFeature.DisableCircularReferenceDetect);  

其中:SerializerFeature.DisableCircularReferenceDetect就是禁止迴圈引用的方案,我們可以通過列舉類SerializerFeature來檢視到底有多少種方式:

[java] view plain copy

  1. public enum SerializerFeature {  
  2.     QuoteFieldNames,  
  3.     UseSingleQuotes,  
  4.     WriteMapNullValue,  
  5.     WriteEnumUsingToString,  
  6.     UseISO8601DateFormat,  
  7.     /** 
  8.      * @since 1.1 
  9.      */  
  10.     WriteNullListAsEmpty,  
  11.     /** 
  12.      * @since 1.1 
  13.      */  
  14.     WriteNullStringAsEmpty,  
  15.     /** 
  16.      * @since 1.1 
  17.      */  
  18.     WriteNullNumberAsZero,  
  19.     /** 
  20.      * @since 1.1 
  21.      */  
  22.     WriteNullBooleanAsFalse,  
  23.     /** 
  24.      * @since 1.1 
  25.      */  
  26.     SkipTransientField,  
  27.     /** 
  28.      * @since 1.1 
  29.      */  
  30.     SortField,  
  31.     /** 
  32.      * @since 1.1.1 
  33.      */  
  34.     @Deprecated  
  35.     WriteTabAsSpecial,  
  36.     /** 
  37.      * @since 1.1.2 
  38.      */  
  39.     PrettyFormat,  
  40.     /** 
  41.      * @since 1.1.2 
  42.      */  
  43.     WriteClassName,  
  44.   
  45.     /** 
  46.      * @since 1.1.6 
  47.      */  
  48.     DisableCircularReferenceDetect,  
  49.   
  50.     /** 
  51.      * @since 1.1.9 
  52.      */  
  53.     WriteSlashAsSpecial,  
  54.       
  55.     /** 
  56.      * @since 1.1.10 
  57.      */  
  58.     BrowserCompatible,  
  59.       
  60.     /** 
  61.      * @since 1.1.14 
  62.      */  
  63.     WriteDateUseDateFormat,  
  64.       
  65.     /** 
  66.      * @since 1.1.15 
  67.      */  
  68.     NotWriteRootClassName,  
  69.       
  70.     /** 
  71.      * @since 1.1.19 
  72.      */  
  73.     DisableCheckSpecialChar,  
  74.       
  75.     /** 
  76.      * @since 1.1.35 
  77.      */  
  78.     BeanToArray  
  79.     ;  
  80.   
  81.     private SerializerFeature(){  
  82.         mask = (1 << ordinal());  
  83.     }  
  84.   
  85.     private final int mask;  
  86.   
  87.     public final int getMask() {  
  88.         return mask;  
  89.     }  
  90.   
  91.     public static boolean isEnabled(int features, SerializerFeature feature) {  
  92.         return (features & feature.getMask()) != 0;  
  93.     }  
  94.   
  95.     public static int config(int features, SerializerFeature feature, boolean state) {  
  96.         if (state) {  
  97.             features |= feature.getMask();  
  98.         } else {  
  99.             features &= ~feature.getMask();  
  100.         }  
  101.   
  102.         return features;  
  103.     }  
  104. }  

相關文章