Java - net.sf.json.JSONException: There is a cycle in the hierarchy!

我們在撰寫相關網頁時,有可能利用到JSONObject或JSONArray來包裝相關的response value

值給前端JavaScript做parse,進而得到相關訊息!當碰到net.sf.json.JSONException: There is a

cycle in the hierarchy!時,有可能是使用JSON上哪裡出了問題?


測試JSON Library版本為json-lib-2.0-jdk15.jar

在這邊撰寫一段簡單的TEST程式

定義一Employees.java及TestJSON.java
public class Employees extends PrimaryBean{
   private int employeeid;
   private String employeename;
   private String department;
   private BigDecimal salary;
   private Employees self;

   //相關setter or getter...略
public boolean equals(Object obj) {
  if (obj instanceof Employees) {
     Employees compare = (Employees) obj;
     return employeeid == compare.getEmployeeid();
  } else {
     return false;
  }
 }

 public int hashCode() {
    return employeeid;
 }
}

public class TestJSON {
   public static void main(String[] args) {
      // TODO Auto-generated method stub
      Employees bean = new Employees();
      Employees bean2 = new Employees();
      bean.setEmployeeid(123);
      bean2.setEmployeeid(123);
      bean.setSelf(bean2);
      JSONObject object = JSONObject.fromObject(bean);
      System.out.println(object);
   }
}

定義一Employees類別,當new一object時並做相關的set values,此時可透過JSONObject的

fromObject來將Employees轉換成一key, value的JSONObject,key為相關的成員變數,而values

為相關set的值。但以此例會出現net.sf.json.JSONException: There is a cycle in the hierarchy!

的錯誤訊息!

追蹤了一下其source code,發現在JSONObject會去檢查相關特別型態的values值是否存在於

cycleSet內了,若已存在則跳出該Exception!! 以此例會走_fromBean( object );

private static JSONObject _fromBean( Object bean ) {
...
if( !addInstance( bean ) ){
          try{
            return jsonConfig.getCycleDetectionStrategy()
                  .handleRepeatedReferenceAsObject( bean );
         }
         ......
}
}

=> addInstance由AbstractJSON父類別宣告
protected static boolean addInstance( Object instance ) {
      return cycleSet.add( instance );
}

也就是說它會去作類別是否相等,以Employees來說若宣告了兩個objects,其equals的依據

employeeid皆相同,此時利用cycleSet加入第一次為true(bean本身),若加入第二次則為false!

第二次指的是當處理bean下的成員變數要設為JSONObject的key map value時,當做到self的

setValue時會執行到

1005行 ==> jsonObject._setInternal( key, value );

2177行 ==> this.properties.put( key, _processValue( value ) );

在過來又回到fromObject,此時method又去判斷該value為int or JSONString or ....

再次執行_fromBean( object ); 此時再add當時set到self成員內的bean2,cycleSet發現Employee

類別下的object已存在了,因此會return false!因此程式便會進入

 return jsonConfig.getCycleDetectionStrategy()
               .handleRepeatedReferenceAsObject( bean );

JsonConfig.java預設會走Strict mode,因此jsonConfig.getCycleDetectionStrategy()物件所呼

叫的為StrictCycleDetectionStrategy的method而非LenientCycleDetectionStrategy
public class JsonConfig {

   private static final CycleDetectionStrategy DEFAULT_CYCLE_DETECTION_STRATEGY =
CycleDetectionStrategy.STRICT;

   private CycleDetectionStrategy cycleDetectionStrategy = DEFAULT_CYCLE_DETECTION_STRATEGY;

   public CycleDetectionStrategy getCycleDetectionStrategy() {
      return cycleDetectionStrategy;
   }
   .....
}

CycleDetectionStrategy.java如下:
private static final class LenientCycleDetectionStrategy extends CycleDetectionStrategy {
public JSONArray handleRepeatedReferenceAsArray( Object reference ) {
         return new JSONArray();
      }
      public JSONObject handleRepeatedReferenceAsObject( Object reference ) {
         return new JSONObject( true );
      }
   }
private static final class StrictCycleDetectionStrategy extends CycleDetectionStrategy {
      public JSONArray handleRepeatedReferenceAsArray( Object reference ) {
         throw new JSONException( "There is a cycle in the hierarchy!" );
      }
      public JSONObject handleRepeatedReferenceAsObject( Object reference ) {
         throw new JSONException( "There is a cycle in the hierarchy!" );
      }
   }

若要防止Exception發生,可設定走
JsonConfig jsonConfig = JsonConfig.getInstance();
jsonConfig.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);
JSONObject object = JSONObject.fromObject(bean);


此時將會印出

{"employeeid":123,"department":"","employeename":"","self":null,"salary":0}

原本self的值應該為{"employeeid:....,此時會為null

或改code為bean2.setEmployeeid(456);

此時印出為

{"employeeid":123,"department":"","employeename":"","self":{"employeeid":456,"department":"","employeename":"","self":null,"salary":0},"salary":0}

留言