java中json对象转换为jsonString的结果中出现$ref的问题分析
背景
在设计开发一个树形数据结果的时候,树形结构如下图
发现通过com.alibaba.fastjson输出的json string中出现了xxx的字样,如下图类似
之前开发中并未遇到过类似的情况。
由于使用了递归处理树形数据结构的元素,所以怀疑是元素之间互相引用,导致输出的结果使用了特殊的表现方式
于是翻了资料w3c的资料,发现确实有这么回事
www.w3cschool.cn/fastjson/fa…
验证
package com.example.designpattern;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
@Slf4j
public class JsonTest {
@Test
public void test() {
Map<String, Person> c = new HashMap<>();
Person p1 = new Person(1, "bob", null);
Person p2 = new Person(2, "tom", p1);
Person p3 = new Person(3, "lihua", null);
c.put("1", p1);
c.put("2", p2);
c.put("3", p3);
//c.put("4", p3);
log.error("c json string is {}", JSONObject.toJSONString(c));
}
@Data
private static class Person {
private int id;
private String name;
private Person bestFriend;
private Person(int id, String name, Person bestFriend) {
this.bestFriend = bestFriend;
this.id = id;
this.name = name;
}
}
}
同一个对象被多个地方引用,但是没有互相引用
如:p1
@Test
public void test(){
Map<String, Person> c=new HashMap<>();
Person p1=new Person(1,"bob",null);
Person p2=new Person(2,"tom",p1);
Person p3=new Person(3,"lihua",null);
c.put("1",p1);
c.put("2",p2);
c.put("3",p3);
//c.put("4", p3);
log.error("c json string is {}",JSONObject.toJSONString(c));
}
结果为 {"1":{"id":1,"name":"bob"},"2":{"bestFriend":{".1"},"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"}}
出现了$ref
如:p3
@Test
public void test(){
Map<String, Person> c=new HashMap<>();
Person p1=new Person(1,"bob",null);
Person p2=new Person(2,"tom",p1);
Person p3=new Person(3,"lihua",null);
c.put("1",p1);
c.put("2",p2);
c.put("3",p3);
//c.put("4", p3);
log.error("c json string is {}",JSONObject.toJSONString(c));
}
结果为:{"1":{"id":1,"name":"bob"},"2":{"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"},"4":{".3"}}
出现了$ref
@Test
public void test() {
Map<String, Person> c = new HashMap<>();
Person p1 = new Person(1, "bob", null);
Person p2 = new Person(2, "tom", null);
Person p3 = new Person(3, "lihua", null);
Person p4 = new Person(4, "mary", null);
// p2.setBestFriend(p1);
// p1.setBestFriend(p2);
//
// p3.setBestFriend(p4);
// p4.setBestFriend(p3);
c.put("1", p1);
c.put("2", p2);
c.put("3", p3);
c.put("4", p3);
log.error("c json string is {}", JSONObject.toJSONString(c));
}
结果为:{"1":{"id":1,"name":"bob"},"2":{"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"},"4":{".3"}} 同样出现了$ref
可见再fastjson中,只要同一个对象出现了多次,再转换为json string时,非首次出现的地方都使用了类似jsonpath的方式表示引用和值
两个对象互相引用
如:p1、p2
@Test
public void test() {
Map<String, Person> c = new HashMap<>();
Person p1 = new Person(1, "bob", null);
Person p2 = new Person(2, "tom", null);
Person p3 = new Person(3, "lihua", null);
p2.setBestFriend(p1);
p1.setBestFriend(p2);
c.put("1", p1);
c.put("2", p2);
c.put("3", p3);
//c.put("4", p3);
log.error("c json string is {}", JSONObject.toJSONString(c));
}
测试结果为:
出现了ref: ".."表示
再加入一组互相引用 p3、p4
@Test
public void test() {
Map<String, Person> c = new HashMap<>();
Person p1 = new Person(1, "bob", null);
Person p2 = new Person(2, "tom", null);
Person p3 = new Person(3, "lihua", null);
Person p4 = new Person(4, "mary", null);
p2.setBestFriend(p1);
p1.setBestFriend(p2);
p3.setBestFriend(p4);
p4.setBestFriend(p3);
c.put("1", p1);
c.put("2", p2);
c.put("3", p3);
c.put("4", p4);
//c.put("4", p3);
log.error("c json string is {}", JSONObject.toJSONString(c));
}
结果为:
这时候已经没办法区分p1、p3是引用的p2还是p4了
可见两个对象互相引用会导致同一个对象出现了1次以上,会导致转换成json string的时候使用$ref
如何关闭这种机制
局部关闭
非循环引用
public void test() {
Map<String, Person> c = new HashMap<>();
Person p1 = new Person(1, "bob", null);
Person p2 = new Person(2, "tom", null);
Person p3 = new Person(3, "lihua", null);
Person p4 = new Person(4, "mary", null);
// p2.setBestFriend(p1);
// p1.setBestFriend(p2);
//
// p3.setBestFriend(p4);
// p4.setBestFriend(p3);
c.put("1", p1);
c.put("2", p2);
c.put("3", p3);
c.put("4", p3);
log.error("c json string is {}", JSONObject.toJSONString(c, SerializerFeature.DisableCircularReferenceDetect));
}
结果为:{"1":{"id":1,"name":"bob"},"2":{"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"},"4":{"id":3,"name":"lihua"}}
成功关闭
全局关闭
非循环引用
public void test() {
JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask();
Map<String, Person> c = new HashMap<>();
Person p1 = new Person(1, "bob", null);
Person p2 = new Person(2, "tom", null);
Person p3 = new Person(3, "lihua", null);
Person p4 = new Person(4, "mary", null);
// p2.setBestFriend(p1);
// p1.setBestFriend(p2);
//
// p3.setBestFriend(p4);
// p4.setBestFriend(p3);
c.put("1", p1);
c.put("2", p2);
c.put("3", p3);
c.put("4", p3);
log.error("c json string is {}", JSONObject.toJSONString(c));
}
结果为:{"1":{"id":1,"name":"bob"},"2":{"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"},"4":{"id":3,"name":"lihua"}} 成功关闭
循环引用
@Test
public void test() {
JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask();
Map<String, Person> c = new HashMap<>();
Person p1 = new Person(1, "bob", null);
Person p2 = new Person(2, "tom", null);
Person p3 = new Person(3, "lihua", null);
Person p4 = new Person(4, "mary", null);
p2.setBestFriend(p1);
p1.setBestFriend(p2);
p3.setBestFriend(p4);
p4.setBestFriend(p3);
c.put("1", p1);
c.put("2", p2);
c.put("3", p3);
c.put("4", p4);
log.error("c json string is {}", JSONObject.toJSONString(c));
}
结果为:
死循环导致栈溢出