Java中JSON把引用相同的对象变为"$ref":问题的分析与解决
后台返回给前端的数据一般是JSON格式的,使用com.alibaba.fastjson时,在把后台的响应数据转化为JSON格式时,具有相同引用的对象会变成" r e f " : " ref": " ref":".list[0]",导致前端解析出现错误。
问题重现
先定义一个类People
People有四个变量,分别是编号、姓名、年龄和喜欢的人,其中变量喜欢的人是一个对象(People类的对象)
public class People {
public Integer id;
public String name;
public Integer age;
public People like;
public People(Integer id,String name,Integer age,People like) {
this.id = id;
this.name = name;
this.age = age;
this.like = like;
}
public void show() {
System.out.println("name:"+this.name);
System.out.println(" id:"+this.id);
System.out.println(" age:"+this.age);
if(this.like != null) {
System.out.println(" like:"+this.like.name+"\n");
} else {System.out.println();}
}
}
public static void main(String[] args) {
}
}
创建一个Map,然后添加数据
在主方法中加入下面的代码
People p1 = new People(1,"张三",11,null);
People p2 = new People(2,"李四",12,p1);
People p3 = new People(3,"王五",13,p1);
Map<String,People> map = new HashMap<String, People>();
map.put(p1.name,p1);
map.put(p2.name,p2);
map.put(p3.name,p3);
map.put("宋六",p3);
map.get(p1.name).show();
map.get(p2.name).show();
map.get(p3.name).show();
map.get("宋六").show();
System.out.println(map);
System.out.println(p2.like+" | "+p3.like);
运行结果如下
name:张三
id:1
age:11
name:李四
id:2
age:12
like:张三
name:王五
id:3
age:13
like:张三
name:王五
id:3
age:13
like:张三
{李四=lin.People@15db9742, 张三=lin.People@6d06d69c, 王五=lin.People@7852e922, 宋六=lin.People@7852e922}
lin.People@6d06d69c | lin.People@6d06d69c
王五和宋六是相同的对象,所以他们的引用地址也是完全相同的
李四和王五的like是相同的,所以他们的like的引用地址也是完全相同的
现在我们把map转为JSON
String text = JSON.toJSONString(map);
JSONObject json = JSONObject.parseObject(text);
System.out.println(json);
结果如下:
{"李四":
{"like":{"name":"张三","id":1,"age":11},
"name":"李四",
"id":2,
"age":12
},
"张三":
{"$ref":"$.李四.like"},
"王五":
{"like":{"$ref":"$.李四.like"},
"name":"王五",
"id":3,
"age":13},
"宋六":
{"$ref":"$.王五"}
}
可以看到,具有相同引用的对象第二次出现时,会变成"$ref":+第一次出现的位置
我们先分析一下原因
分析
出现这个问题的原因是fastjson有循环引用检测机制
循环引用:
- 当一个对象包含另一个对象时,fastjson就会把该对象解析成引用
- 当一个对象和另一个对象完全相同时,fastjson同样会把该对象解析成引用
解决方法
1.禁止循环引用检测
我们在组装返回给前端的数据时,如果需要多次使用相同的对象,可以在组装数据时禁止fastjson的循环引用检测,禁用后,fastjson就不会把相同的对象解析为引用了
使用DisableCircularReferenceDetect来禁止循环引用检测
People p1 = new People(1,"张三",11,null);
//需要第二次使用对象p1
String text1 = JSON.toJSONString(p1,SerializerFeature.DisableCircularReferenceDetect);
People p4 = JSON.parseObject(text1, People.class);
People p2 = new People(2,"李四",12,p4);
//需要第三次使用对象p1
String text2 = JSON.toJSONString(p1,SerializerFeature.DisableCircularReferenceDetect);
People p5 = JSON.parseObject(text2, People.class);
People p3 = new People(3,"王五",13,p5);
Map<String,People> map = new HashMap<String, People>();
map.put(p1.name,p1);
map.put(p2.name,p2);
map.put(p3.name,p3);
//需要第二次使用对象p2
String text3 = JSON.toJSONString(p2,SerializerFeature.DisableCircularReferenceDetect);
People p6 = JSON.parseObject(text3, People.class);
map.put("宋六",p6);
System.out.println("p2的like:"+p2.like+" | "+"p3的like:"+p3.like);
System.out.println("map中的李四:"+map.get("李四")+" | "+"map中的宋六:"+map.get("宋六"));
System.out.println("map中的李四的like:"+map.get("李四").like+" | "+"map中的宋六的like:"+map.get("宋六").like);
String text4 = JSON.toJSONString(map);
JSONObject json = JSONObject.parseObject(text4);
System.out.println(json);
现在我们再运行一下代码,看一下组装的map
p2的like:lin.People@6438a396 | p3的like:lin.People@e2144e4
map中的李四:lin.People@6477463f | map中的宋六:lin.People@3d71d552
map中的李四的like:lin.People@6438a396 | map中的宋六的like:lin.People@1cf4f579
{"李四":{"like":{"name":"张三","id":1,"age":11},"name":"李四","id":2,"age":12},"张三":{"name":"张三","id":1,"age":11},"王五":{"like":{"name":"张三","id":1,"age":11},"name":"王五","id":3,"age":13},"宋六":{"like":{"name":"张三","id":1,"age":11},"name":"李四","id":2,"age":12}}
可以看到问题已经解决
——
2.深度复制
深度复制和禁止循环引用检测的原理是相似的,都是先创建一个全新的对象,再使用它
我们先添加一个深度复制的公共方法,该方法使用序列化来实现深度复制(深度拷贝)
我们先让People类实现Serializable接口
public class People implements Serializable
然后添加下面的静态方法
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
然后修改主方法
People p1 = new People(1,"张三",11,null);
//需要第二次使用对象p1
People p2 = new People(2,"李四",12,(People)clone(p1));
//需要第三次使用对象p1
People p3 = new People(3,"王五",13,(People)clone(p1));
Map<String,People> map = new HashMap<String, People>();
map.put(p1.name,p1);
map.put(p2.name,p2);
map.put(p3.name,p3);
//需要第二次使用对象p2
map.put("宋六",(People)clone(p2));
System.out.println("p2的like:"+p2.like+" | "+"p3的like:"+p3.like);
System.out.println("map中的李四:"+map.get("李四")+" | "+"map中的宋六:"+map.get("宋六"));
System.out.println("map中的李四的like:"+map.get("李四").like+" | "+"map中的宋六的like:"+map.get("宋六").like);
String text4 = JSON.toJSONString(map);
JSONObject json = JSONObject.parseObject(text4);
System.out.println(json);
运行主方法
p2的like:lin.People@58372a00 | p3的like:lin.People@5fd0d5ae
map中的李四:lin.People@7ba4f24f | map中的宋六:lin.People@2d98a335
map中的李四的like:lin.People@58372a00 | map中的宋六的like:lin.People@16b98e56
{"李四":{"like":{"name":"张三","id":1,"age":11},"name":"李四","id":2,"age":12},"张三":{"name":"张三","id":1,"age":11},"王五":{"like":{"name":"张三","id":1,"age":11},"name":"王五","id":3,"age":13},"宋六":{"like":{"name":"张三","id":1,"age":11},"name":"李四","id":2,"age":12}}
可以看到问题已经解决
——
最后粘上所有的代码
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class People implements Serializable{
private static final long serialVersionUID = 1L;
public Integer id;
public String name;
public Integer age;
public People like;
public People(Integer id,String name,Integer age,People like) {
this.id = id;
this.name = name;
this.age = age;
this.like = like;
}
public void show() {
System.out.println("name:"+this.name);
System.out.println(" id:"+this.id);
System.out.println(" age:"+this.age);
if(this.like != null) {
System.out.println(" like:"+this.like.name+"\n");
} else {System.out.println();}
}
@SuppressWarnings("unchecked")
public static <T extends Serializable> T clone(T obj){
T cloneObj = null;
try {
//写入字节流
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream obs = new ObjectOutputStream(out);
obs.writeObject(obj);
obs.close();
//分配内存,写入原始对象,生成新对象
ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
ObjectInputStream ois = new ObjectInputStream(ios);
//返回生成的新对象
cloneObj = (T) ois.readObject();
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
return cloneObj;
}
public static void main(String[] args) {
People p1 = new People(1,"张三",11,null);
//需要第二次使用对象p1
People p2 = new People(2,"李四",12,(People)clone(p1));
//需要第三次使用对象p1
People p3 = new People(3,"王五",13,(People)clone(p1));
Map<String,People> map = new HashMap<String, People>();
map.put(p1.name,p1);
map.put(p2.name,p2);
map.put(p3.name,p3);
//需要第二次使用对象p2
map.put("宋六",(People)clone(p2));
System.out.println("p2的like:"+p2.like+" | "+"p3的like:"+p3.like);
System.out.println("map中的李四:"+map.get("李四")+" | "+"map中的宋六:"+map.get("宋六"));
System.out.println("map中的李四的like:"+map.get("李四").like+" | "+"map中的宋六的like:"+map.get("宋六").like);
String text4 = JSON.toJSONString(map);
JSONObject json = JSONObject.parseObject(text4);
System.out.println(json);
}
}