JSON有一個非常經典的問題:JSONException: There is a cycle in the hierarchy!俗稱死循環.解決這個問題至少有三種以上的辦法,總之一句話就是過濾.今天嘗試着從
反射的角度來闡述和解決這個問題.
一.“反射重組(姑且這么叫吧)”
廢話不多說,直接上代碼.以下代碼,預設有兩個實體類,Company及Product,它們為一對多雙向關聯映射。類Product中有屬性company與之關聯類Company.現在,需要以
列表形式展示Product,后台以JSON格式傳遞數據。
1 class 2 { 3 @RequestMapping 4 @ResponseBody 5 public void getproduct(HttpServletRequest request,HttpServletResponse response,Product product) throws Exception{ 6 PageBean<Product, Product> pageBean = this.getPageBean(request); 7 pageBean.setSearchCondObj(product); 8 PageBean<Product, Product> bean = this.productService.getProduct(pageBean); 9 Map<String, Object> map=new HashMap<String, Object>(); 10 JsonConfig config = new JsonConfig(); 11 12 //屏蔽掉相關聯的實體屬性,以及不需要在列表中展示的屬性 13 14 config.setExcludes(new String[] {"description","companyperson","comment","productitems","companycontact"}); 15 // 把列表顯示需要的實體屬性傳過去 16 17 config.registerJsonValueProcessor(Company.class, //調用registerJsonValueProcessor構造方法,初始化參數 18 new ObjectJsonValueProcessor(new String[]{"name","id"},Company.class)); 19 20 Map<String, Object> jsonMap = new HashMap<String, Object>(); 21 jsonMap.put("total", bean.getSize()); 22 jsonMap.put("rows", bean.getSource()); 23 JSONObject result = JSONObject.fromObject(jsonMap, config); 24 this.outPrint(response, request, result.toString()); 25 } 26 }
為了避免陷入"net.sf.json.JSONException: There is a cycle in the hierarchy!",以下這段代碼是核心代碼,它的核心是反射重組.代碼出處為網絡,非本人原創.
我添加了注釋,以便理解查看.
1 package com.project.pojo; 2 3 import java.beans.PropertyDescriptor; 4 import java.lang.reflect.Method; 5 6 import net.sf.json.JSONObject; 7 import net.sf.json.JsonConfig; 8 import net.sf.json.processors.JsonValueProcessor; 9 10 /** 11 * 解決JSONObject.fromObject拋出"There is a cycle in the hierarchy"異常導致死循環的解決辦法 12 * 以及實體屬性無法傳遞的問題 13 * 此段代碼為網絡資料,非原創 14 */ 15 public class ObjectJsonValueProcessor implements JsonValueProcessor { 16 17 /** 18 * 需要留下的字段數組 19 */ 20 private String[] properties; 21 22 /** 23 * 需要做處理的復雜屬性類型 24 */ 25 private Class<?> clazz; 26 27 /** 28 * 構造方法,參數必須 29 * @param properties 30 * @param clazz 31 */ 32 public ObjectJsonValueProcessor(String[] properties,Class<?> clazz){ 33 this.properties = properties; 34 this.clazz =clazz; 35 } 36 37 @Override 38 public Object processArrayValue(Object value, JsonConfig arg1) { 39 PropertyDescriptor pd = null; 40 Method method = null; 41 StringBuffer json = new StringBuffer("{"); 42 try{ 43 for(int i=0;i<properties.length;i++){ 44 pd = new PropertyDescriptor(properties[i], clazz); 45 method = pd.getReadMethod(); 46 String v = String.valueOf(method.invoke(value)); 47 json.append("'"+properties[i]+"':'"+v+"'"); 48 json.append(i != properties.length-1?",":""); 49 } 50 json.append("}"); 51 }catch (Exception e) { 52 e.printStackTrace(); 53 } 54 return JSONObject.fromObject(json.toString()); 55 return null; 56 } 57 58 @Override 59 public Object processObjectValue(String key, Object value, JsonConfig jsonConfig) { 60 //key為實體關聯字段,即外鍵 61 PropertyDescriptor pd = null; 62 Method method = null; 63 StringBuffer json = new StringBuffer("{"); 64 try{ 65 for(int i=0;i<properties.length;i++){ 66 pd = new PropertyDescriptor(properties[i], clazz); 67 //反射:通過類PropertyDescriptor可以得到properties數組即相關聯的實體中需要傳遞的屬性,它的名稱,類型以及getter,setter方法 68 method = pd.getReadMethod(); //得到屬性的讀取方法,即getter() 69 if (value != null){ 70 71 String v = String.valueOf(method.invoke(value));//執行getter(),當然也可以看出這里value 72 //即是一個實體類的字節碼,在這里是Company.class,然后在組裝JSON格式的字符串 73 74 json.append("'" + properties[i] + "':'" + v + "'"); 75 json.append(i != properties.length - 1 ? "," : ""); 76 77 } 78 } 79 80 json.append("}"); 81 System.out.println("json = "+json.toString()); 82 }catch (Exception e) { 83 e.printStackTrace(); 84 } 85 return JSONObject.fromObject(json.toString()); 86 } 87 }
為了更好的驗證以及看清processObjectValue(),貼一段測試代碼及結果:
1 class 2 { 3 public static void main(String[] args) 4 { 5 @Override 6 public Object processObjectValue(String key, Object value, JsonConfig jsonConfig) { 7 System.out.println("key :"+key); 8 PropertyDescriptor pd = null; 9 Method method = null; 10 StringBuffer json = new StringBuffer("{"); 11 try{ 12 for(int i=0;i<properties.length;i++){ 13 pd = new PropertyDescriptor(properties[i], clazz); 14 System.out.println("pd :"+pd); 15 16 method = pd.getReadMethod(); 17 18 if (value != null){ 19 System.out.println("value :"+value); 20 String v = String.valueOf(method.invoke(value)); 21 System.out.println("v :"+v); 22 json.append("'" + properties[i] + "':'" + v + "'"); 23 json.append(i != properties.length - 1 ? "," : ""); 24 } 25 26 } 27 28 json.append("}"); 29 System.out.println("json = "+json.toString()); 30 }catch (Exception e) { 31 e.printStackTrace(); 32 } 33 return JSONObject.fromObject(json.toString()); 34 } 35 } 36 }
測試結果:

在執行
config.registerJsonValueProcessor(Company.class,new ObjectJsonValueProcessor(new String[]{"name","id"},Company.class));
這段代碼的時候,僅僅只是對ObjectJsonValueProcessor作了初始化,真正執行類ObjectJsonValueProcessor中processObjectValue(),還是在這里:
JSONObject result = JSONObject.fromObject(jsonMap, config);
通過觀察JSONObject源碼可以看到,在fromObject(jsonMap, config)中調用了fromDynaBean(DynaBean bean, JsonConfig jsonConfig),從名字中可以看出,
參數為一個動態JAVABEAN,即為在processObjectValue()中,通過反射得到關聯實體的屬性,然后動態組裝。
1 public static JSONObject fromObject(Object object, JsonConfig jsonConfig) 2 { 3 if ((object == null) || (JSONUtils.isNull(object))) 4 return new JSONObject(true); 5 if ((object instanceof Enum)) 6 throw new JSONException("'object' is an Enum. Use JSONArray instead"); 7 if (((object instanceof Annotation)) || ((object != null) && (object.getClass().isAnnotation()))) 8 { 9 throw new JSONException("'object' is an Annotation."); 10 }if ((object instanceof JSONObject)) 11 return _fromJSONObject((JSONObject)object, jsonConfig); 12 if ((object instanceof DynaBean)) 13 return _fromDynaBean((DynaBean)object, jsonConfig);//執行這里 14 if ((object instanceof JSONTokener)) 15 return _fromJSONTokener((JSONTokener)object, jsonConfig); 16 if ((object instanceof JSONString)) 17 return _fromJSONString((JSONString)object, jsonConfig); 18 if ((object instanceof Map)) 19 return _fromMap((Map)object, jsonConfig); 20 if ((object instanceof String)) 21 return _fromString((String)object, jsonConfig); 22 if ((JSONUtils.isNumber(object)) || (JSONUtils.isBoolean(object)) || (JSONUtils.isString(object))) 23 { 24 return new JSONObject(); 25 }if (JSONUtils.isArray(object)) { 26 throw new JSONException("'object' is an array. Use JSONArray instead"); 27 } 28 return _fromBean(object, jsonConfig); 29 }
然后再執行processObjectValue(),得到關聯實體需要的屬性組成的JSON對象,然后再調用 setValue(jsonObject, key, value, value.getClass(), jsonConfig, bypass)
組裝完整的JSON對象
1 if (!exclusions.contains(key)) 2 { 3 Object value = entry.getValue(); 4 if ((jsonPropertyFilter == null) || (!jsonPropertyFilter.apply(map, key, value))) 5 { 6 if (value != null) { 7 JsonValueProcessor jsonValueProcessor = jsonConfig.findJsonValueProcessor(value.getClass(), key); 8 9 if (jsonValueProcessor != null) { 10 value = jsonValueProcessor.processObjectValue(key, value, jsonConfig);//執行processObjectValue(),得到關聯實體需要的屬性組成的JSON對象 11 bypass = true; 12 if (!JsonVerifier.isValidJsonValue(value)) { 13 throw new JSONException("Value is not a valid JSON value. " + value); 14 } 15 } 16 setValue(jsonObject, key, value, value.getClass(), jsonConfig, bypass);//組裝完整的JSON對象 17 }
二.其它方法
通過JsonValueProcessor解決JSON死循環的問題,到此基本描述清楚了.思慮再在一,我覺得還有必要羅嗦幾句,即講一講其它三種解決JSON死循環的方法,不然怎能看出用
JsonValueProcessor解決的好處呢?
1:過濾屏蔽,如:
1 1 JsonConfig config = new JsonConfig(); 2 2 config.setExcludes(new String[] {"company"});
如此可以過濾掉不需要的屬性以及關聯實體屬性,這樣當然不會報錯,但是如果我需要展示"company"的屬性呢?這樣顯然無法滿足需求。
2:使用JSON屬性過濾器PropertyFilter()
1 config.setJsonPropertyFilter(new PropertyFilter() { 2 3 @Override 4 /** 5 * argo:當前進行操作的實體,如:product 6 * arg1:實體屬性 7 * arg2:實體屬性的類型 8 */ 9 public boolean apply(Object arg0, String arg1, Object arg2) { 10 if(arg1.equals("company")){ 11 return true;//表示過濾掉此屬性 12 } 13 return false;//表示正常操作 14 } 15 });
此方法實際上跟方法1所能達到的效果一樣,但是更為復雜。當然可以在[if(arg1.equals("company"))]這里通過反射得到setter方法,重新設置屬性值,但是反射不能改
變屬性的類型和方法參數類型,所以還是不能避免死循環。
3:使用JsonValueProcessor()
1 config.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT); //首先避免掉死循環 2 config.setExcludes(new String[]{"handler","hibernateLazyInitializer"}); //設置延遲加載 3 config.registerJsonValueProcessor(Date.class,new JsonValueProcessor() { 4 public Object processObjectValue(String arg0, Object arg1, JsonConfig arg2) { 5 SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 6 Date d=(Date) arg1; 7 return sdf.format(d); 8 } 9 public Object processArrayValue(Object arg0, JsonConfig arg1) { 10 return null; 11 } 12 });
這段代碼完全能夠實現既避免死循環又得到"company"的全部屬性。功能上沒有問題,但是效率上有大問題。盡管我在實體和屬性層面上都設置了延遲加載,但是product還
是通過company把所有的區域信息加載出來,所形成的JSON字符串足足有3.5MB,嚴重影響了效率。
三.小結
其它的方法應該還有,我甚至見過有人新建一個JAVABean或新建一個內部類,在通過循環賦值構建一個沒有關聯實體的單獨的JAVABean,來作為構建JSON對象的實體類。既
然有這么多的方法可能解決問題,就應該尋求一個高效的解決方法,我認為使用這種姑且叫做“反射重組”的方法是比較高效的,它可以代碼重用,可以有效得到需要的內容。歡
迎討論,輕拍。
