背景
应用场景是这样的:前端需要构建一个多层 JSON 嵌套的数据结构,而我在使用 fastjson
构建 JSON 结构传给前端后,显示循环引用异常。
问题
最终需要构建的 JOSN 结构如下:
问题复现
构建 JSON 的代码如下:
@GetMapping("t40")
public void test40(){
JSONObject jsonObject = new JSONObject();
JSONObject json = new JSONObject();
JSONObject superPackage = new JSONObject();
JSONObject fastPackage = new JSONObject();
json.put("superPackage", superPackage);
json.put("fastPackage", fastPackage);
jsonObject.put("year", json);
jsonObject.put("month", json);
System.out.println(JSON.toJSONString(jsonObject));
}
输出,出现循环引用问题:
{"month":{"superPackage":{},"fastPackage":{}},"year":{"$ref":"$.month"}}
问题原因
这个输出结果是由于 JSONObject
类在处理循环引用时采用了特殊的处理方式。循环引用是指在 JSON 对象中存在相互引用的情况。
在代码中,将 superPackage
和 fastPackage
同时添加到 monthJson
和 yearJson
时,它们实际上形成了一个循环引用。为了处理这种情况, JSONObject
类将循环引用转换为特殊的引用标记,即 $ref
。
具体地说,将 superPackage
添加到 yearJson
时,它会检测到它已经存在于 monthJson
中,因此不会将完整的对象添加到 yearJson
中,而是创建一个特殊的引用标记 {"$ref":"$.month.superPackage"}
。这表示 yearJson
中的 superPackage
属性引用了 monthJson
中的 superPackage
属性。
同样地,将 fastPackage
添加到 yearJson
时,它也会创建一个特殊的引用标记 {"$ref":"$.month.fastPackage"}
,表示 yearJson
中的 fastPackage
属性引用了 monthJson
中的 fastPackage
属性。
这种处理方式是为了避免无限递归和循环引用导致的问题,并保证 JSON 对象的正确性。当使用 JSONObject
的 toString()
方法将其转换为字符串时,会自动应用这种引用标记的处理机制。
所以最终输出的 JSON 字符串中,yearJson
中的 superPackage
属性和 fastPackage
属性被替换为了对 monthJson
中相应属性的引用标记 {"$ref":"$.month.superPackage"}
和 {"$ref":"$.month.fastPackage"}
,以表示它们与 monthJson
中的属性是相同的对象。
问题解决
新建 JSONObject 对象,避免复用。
@GetMapping("/t34")
public void test34(){
JSONObject jsonObject = new JSONObject();
JSONObject monthJson = new JSONObject();
JSONObject yearJson = new JSONObject();
JSONObject superPackage = new JSONObject();
JSONObject fastPackage = new JSONObject();
JSONObject monthSuperPackage = new JSONObject();
JSONObject monthFastPackage = new JSONObject();
monthJson.put("superPackage", monthSuperPackage);
monthJson.put("fastPackage", monthFastPackage);
yearJson.put("superPackage", superPackage);
yearJson.put("fastPackage", fastPackage);
jsonObject.put("year", yearJson);
jsonObject.put("month", monthJson);
System.out.println(JSON.toJSONString(jsonObject));
}
输出,结果正常:
{"month":{"superPackage":{},"fastPackage":{}},"year":{"superPackage":{},"fastPackage":{}}}
拓展
使用 fastjson
创建的 JSONArray 也存在这种特殊的引用方式。
这里我们创建一个 JSONArray,使用新的变量接收后,此时我们反转新创建的 JSONArray:
@GetMapping("t40")
public void test40(){
JSONObject jsonObject1 = new JSONObject();
jsonObject1.put("1", "1");
JSONObject jsonObject2 = new JSONObject();
jsonObject2.put("2", "2");
JSONObject jsonObject3 = new JSONObject();
jsonObject3.put("3", "3");
JSONArray jsonArray = new JSONArray();
jsonArray.add(jsonObject1);
jsonArray.add(jsonObject2);
jsonArray.add(jsonObject3);
System.out.println("jsonArray为:" + jsonArray);
// 创建一个新的JSONArray,并把jsonArray赋值给它
JSONArray array = jsonArray;
// 反转新的JSONArray
Collections.reverse(array);
System.out.println("此时的jsonArray为:" + jsonArray);
}
输出:
jsonArray为:[{"1":"1"},{"2":"2"},{"3":"3"}]
此时的jsonArray为:[{"3":"3"},{"2":"2"},{"1":"1"}]
可以看到对于名为 jsonArray 的变量我们并没有反转,反转的是临时创建的 array 变量,但我们输出 jsonArray 后发现它被反转了。这就是 fastjson 特殊的引用处理方式导致的。虽然创建了新的变量,但其指向还是原来的那个。
总结
FASTJSON 引用方式较为特殊,使用时需要注意别导致循环引用。