java集合转换成json时问题和解决方法
json+hibernate死循环问题的一点见解,有需要的朋友可以参考下。
【问题】如题所示,在我们使用hibernate框架而又需要将对象转化为json的时候,如果配置了双向的关联关系,就会出现这个死循环问题
异常信息:
Method public java.lang.String org.apache.commons.lang.exception.NestableRuntimeException.getMessage(int) threw an exception when invoked on net.sf.json.JSONException: There is a cycle in the hierarchy!
The problematic instruction:
----------
==> ${msgs[0][0]} [on line 76, column 25 in org/apache/struts2/dispatcher/error.ftl]
---------- Java backtrace for programmers:
----------
freemarker.template.TemplateModelException: Method public java.lang.String org.apache.commons.lang.exception.NestableRuntimeException.getMessage(int) threw an exception when invoked on net.sf.json.JSONException: There is a cycle in the hierarchy!
at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:130)
at freemarker.ext.beans.SimpleMethodModel.get(SimpleMethodModel.java:138)
at freemarker.core.DynamicKeyName.dealWithNumericalKey(DynamicKeyName.java:111)
at freemarker.core.DynamicKeyName._getAsTemplateModel(DynamicKeyName.java:90)
at freemarker.core.Expression.getAsTemplateModel(Expression.java:89)
at freemarker.core.Expression.getStringValue(Expression.java:93)
at freemarker.core.DollarVariable.accept(DollarVariable.java:76)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.MixedContent.accept(MixedContent.java:92)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.IfBlock.accept(IfBlock.java:82)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.IfBlock.accept(IfBlock.java:82)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.MixedContent.accept(MixedContent.java:92)
at freemarker.core.Environment.visit(Environment.java:209)
at freemarker.core.Environment.process(Environment.java:189)
at freemarker.template.Template.process(Template.java:237)
at org.apache.struts2.dispatcher.Dispatcher.sendError(Dispatcher.java:748)
at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:505)
at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77)
at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:293)
at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:861)
at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:606)
at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.lang.NullPointerException
at freemarker.ext.beans.SimpleMemberModel.unwrapArguments(SimpleMemberModel.java:85)
at freemarker.ext.beans.SimpleMethodModel.exec(SimpleMethodModel.java:106)
... 33 more
关键字是net.sf.json.JSONException: There is a cycle in the hierarchy!,意思是在层次关系里有一个循环
【原因】为什么会这样呢?原因在于你要转化的对象里配置了对另外一个对象的关联,而那个对象里又配置了对你这个对象的关联。比如我的两个类叫做Shop(商店)和Staff(员工),一个商店可以有多个员工,所以我给这两个对象配置了双向的一对多和多对一的关联关系。这时候问题就出现了,JSON lib在把shop对象转化为json字符串的时候,发现shop里有个Set<Staff>,它就会去级联的把Set<Staff>转化为json字符串,在它遍历Set的时候,发现Staff里又有一个Shop对象,这时候它又会去尝试把shop转化为json字符串,然后就发现shop里又有Set<Staff>,如此周而复始,就形成了死循环。
【解决】问题清楚了,如何解决呢?我百度了一下,也找到了数十条的资料,但大都只是都说明了要用jsonConfig.setJsonPropertyFilter(new PropertyFilter(){}),而对于其中的参数和if语句该如何写,并没有一个说的很明白的。为此我查看了json-lib的源码,并进行了尝试,总结如下:
1,思路是在JSONObject把Shop对象转化为json字符串的时候,在中间加一道过滤,如果当前要转化的属性是Set并且属性名是staffs,那么就进行过滤
2,代码如下:
Map<String, Object> map=new HashMap<String, Object>();
map.put("shops", list);
map.put("total", total); JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setJsonPropertyFilter(new PropertyFilter() {
public boolean apply(Object obj, String name, Object value) {
if(obj instanceof Set||name.equals("staffs")){
return true;
}else{
return false;
}
}
}); return JSONObject.fromObject(map,jsonConfig);
如上,PropertyFilter是json-lib提供的进行属性过滤的一个接口,具体的实现是由apply方法做的,那么我们就需要重写apply方法。
此方法有三个参数,第一个是Object类型的,是你要过滤的属性的类型;第二个参数是String类型,是你要过滤的属性的名称;第三个参数是Object类型的,是你要过滤的属性的值(值可能是String或其它类型的,所以用Object)。 返回值是boolean类型的,返回true;就是进行过滤,返回false,就是不进行过滤。
if语句的写法就要根据实际的需要了,比如说我这里要解决死循环,就要实现把Shop里的Set<Staff> staffs属性过滤掉,那我的if语句就应该如上面那样写。总而言之就是JSON-lib在转化的时候,会对每个属性都调用这个apply方法,这样我们就要根据实际的业务需要,如果当前属性符合你的if条件,那你就要返回true,进行过滤。是使用||还是&&也要根据实际而定,比如你的Shop里有两个Set,那你就要使用&&。
这样配置后,再测试,就发现获取Shop的时候死循环问题已经不再出现了。
3,同理,Staff端也应该进行类似的配置
Map<String,Object> map=new HashMap<String, Object>();
map.put("staffs", list);
map.put("total", total); JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setExcludes(new String[]{"handler","hibernateLazyInitializer"});
jsonConfig.setJsonPropertyFilter(new PropertyFilter() {
public boolean apply(Object obj, String name, Object value) {
if(obj instanceof Shop&&name.equals("shop")){
return true;
}else{
return false;
}
}
}); return JSONObject.fromObject(map,jsonConfig);
经过测试,也是没有问题的。
这里大家也可以看见jsonConfig.setExcludes(new String[]{"handler","hibernateLazyInitializer"});,这一行是为了防止hibernate延迟加载造成的异常而设置的。
4,到这里大功告成了吗?不,我在测试的时候发现了一个更严重的问题,如果按照上面做这样配置,那我获取shop的时候,生成的json字符串里staffs的Set不见了;获取Staff的时候,它的属性shop在json字符串里也不见了!稍加分析就可以知道这是上面配置造成的。按上面的配置,Shop里的Set<Staff>被过滤掉了,“过滤掉”的含义不是不级联的转化Staff里的Shop了,而是直接连Set<Staff>都不转化了。这可坏了,我配置双向关联关系就是为了关联显示,你把我的属性过滤掉了,那我还配置双向关联干嘛?我还这么大费周章的来解决死循环干嘛?
那么这个问题该如何解决呢?其实仔细一想,也不难,大家看我把Shop的配置改成下面这样
Map<String, Object> map=new HashMap<String, Object>();
map.put("shops", list);
map.put("total", total); JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setJsonPropertyFilter(new PropertyFilter() {
public boolean apply(Object obj, String name, Object value) {
if(obj instanceof Staff||name.equals("shop")){
return true;
}else{
return false;
}
}
}); return JSONObject.fromObject(map,jsonConfig);
这样就可以获取到了,为什么呢?因为这样配置的话,在将Shop里的Set<Staff> staffs转化的时候,我们不过滤;而在将staffs里的每个Staff里的shop转化的时候,我们进行过滤,这样就既解决了死循环问题,又避免了Shop里的staffs被过滤掉的问题。
同理,Staff要这样配置
Map<String,Object> map=new HashMap<String, Object>();
map.put("staffs", list);
map.put("total", total); JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setExcludes(new String[]{"handler","hibernateLazyInitializer"});
jsonConfig.setJsonPropertyFilter(new PropertyFilter() {
public boolean apply(Object obj, String name, Object value) {
if(obj instanceof Set||name.equals("staffs")){
return true;
}else{
return false;
}
}
}); return JSONObject.fromObject(map,jsonConfig);
可是我测试的时候却发现得到的字符串里只有total,staffs没有了?大家可能已经明白了,不仅Shop里的staffs被过滤掉了,map里的staffs也被过滤掉了。解决也很简单,把map.put("staffs",list);改成map.put("list",list);就行了,就是换个名字。【扩展】到这里,应该能解决大家的问题了。另外还有一种方法也要提一下,
JsonConfig jsonConfig = new JsonConfig();
jsonConfig.setIgnoreDefaultExcludes(false); //设置默认忽略
jsonConfig.setCycleDetectionStrategy(CycleDetectionStrategy.LENIENT);//设置循环策略为忽略 解决json最头疼的问题 死循环
jsonConfig.setExcludes(new String[] {"staffs"});//此处是亮点,只要将所需忽略字段加到数组中即可
这种配置也是比较好理解的,但也要注意属性过滤问题,Shop过滤shop,Staff过滤staffs。
java集合转换成json时问题和解决方法的更多相关文章
- Java对象转换成xml对象和Java对象转换成JSON对象
1.把Java对象转换成JSON对象 apache提供的json-lib小工具,它可以方便的使用Java语言来创建JSON字符串.也可以把JavaBean转换成JSON字符串. json-lib的核心 ...
- C#中对象,字符串,dataTable、DataReader、DataSet,对象集合转换成Json字符串方法。
C#中对象,字符串,dataTable.DataReader.DataSet,对象集合转换成Json字符串方法. public class ConvertJson { #region 私有方法 /// ...
- python2.7字典转换成json时中文字符串变成unicode的问题:
参考:http://blog.csdn.net/u014431852/article/details/53058951 编码问题: python2.7字典转换成json时中文字符串变成unicode的 ...
- 将java类的泛型集合转换成json对象
一般用extjs开发传输都是用json比较多,这个将来大家也许会用到... ConvertJsonUtils.java package com.sunweb.util.jsonfactory; imp ...
- JSON--List集合转换成JSON对象
转自:http://www.cnblogs.com/xmaomao/p/3184542.html 1. 简单的手动放置 键值对 到JSONObject,然后在put到JSONArray对象里 List ...
- Java对象转换成Json字符串是无法获得对应字段名
问题: 代码中已经标注 @JSONField(name = "attrs") private String abc; public String getA() { return a ...
- 如何将java对象转换成json数据
package cn.hopetesting.com.test;import cn.hopetesting.com.domain.User;import com.fasterxml.jackson.c ...
- java对象转换成json
package com.bjs.acrosstime.utils; import java.util.ArrayList; import java.util.Date; import java.uti ...
- FastJson将Java对象转换成json
确保环境依赖都配置好! 1.在pom.xml导入依赖 <dependency> <groupId>com.alibaba</groupId> <artifac ...
随机推荐
- javascript数组遍历for与for in区别详解
js中遍历数组的有两种方式 复制代码代码如下: var array=['a']//标准的for循环for(var i=1;i<array.length;i++){ alert(array[ ...
- Ubuntu 16.04特性及使用基本方法
十招让Ubuntu 16.04用起来更得心应手 Ubuntu 16.04 LTS的这十项新功能,每个Ubuntu用户必须要知道! Ubuntu 16.04 LTS安装好需要设置的15件事
- ThreadLocal(四) : FastThreadLocal原理
一.ThreadLocal的原理以及存在的问题 a. 每个线程内部维护了一个ThreadLocal.ThreadLocalMap类型的变量 b. ThreadLocalMap 的 key 为 Thre ...
- StringBuffer、StringBuilder
相信大家都知道StringBuffer.StringBuilder,但是这两个的用法都差不多,到底有什么区别呢,这也是面试的时候问的比较多的一道题,这里我就来说说,这两个的区别结合String来说~ ...
- python3爬虫-分析Ajax,抓取今日头条街拍美图
# coding=utf-8 from urllib.parse import urlencode import requests from requests.exceptions import Re ...
- 试着利用BAPI 寻找F-59创建凭证的函数
功能块代码 F-59开发类 FIBP事务说明 支付请求 事物:FBP1Screen 0BKPF-BLART = AB________________________________________从程 ...
- checked 和 unchecked 基元类型操作
对基元类型执行的许多算术运算都可能造成溢出: Byte b = ; b = (Byte) (b + ); // b 现在包含 44(或者十六进制值 2C) 重要提示:执行上述算术运算时,第一步要求所有 ...
- LeetCode:对角线遍历【498】
LeetCode:对角线遍历[498] 题目描述 给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示. 示例: 输入: [ [ ...
- Android:日常学习笔记(2)——分析第一个Android应用程序
Android:日常学习笔记(2)——分析第一个Android应用程序 Android项目结构 整体目录结构分析 说明: 除了APP目录外,其他目录都是自动生成的.APP目录的下的内容才是我们的工作重 ...
- 小数据池、is 和 ==的区别
小数据池,在一定情况下出现内存共享(只有int 和 str 才有的) is 和 ==的区别 id() 打印数据的地址 a = 'hello' b = 'hello' print(a = ...