JSONObject循环引用异常

郎家岭伯爵 2023年07月07日 391次浏览

背景

应用场景是这样的:前端需要构建一个多层 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 对象中存在相互引用的情况。

在代码中,将 superPackagefastPackage 同时添加到 monthJsonyearJson 时,它们实际上形成了一个循环引用。为了处理这种情况, JSONObject 类将循环引用转换为特殊的引用标记,即 $ref

具体地说,将 superPackage 添加到 yearJson 时,它会检测到它已经存在于 monthJson 中,因此不会将完整的对象添加到 yearJson 中,而是创建一个特殊的引用标记 {"$ref":"$.month.superPackage"}。这表示 yearJson 中的 superPackage 属性引用了 monthJson 中的 superPackage 属性。

同样地,将 fastPackage 添加到 yearJson 时,它也会创建一个特殊的引用标记 {"$ref":"$.month.fastPackage"},表示 yearJson 中的 fastPackage 属性引用了 monthJson 中的 fastPackage 属性。

这种处理方式是为了避免无限递归和循环引用导致的问题,并保证 JSON 对象的正确性。当使用 JSONObjecttoString() 方法将其转换为字符串时,会自动应用这种引用标记的处理机制。

所以最终输出的 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 引用方式较为特殊,使用时需要注意别导致循环引用