Mongoosej.Blog.Software.Programing Language.Java.Framework.Utils.JSON.Fastjson


FAQ

循环引用、重复引用相关序列化

重复引用就是一个集合/对象中的多个元素/属性同时引用同一对象,循环引用就是集合/对象中的多个元素/属性存在相互引用导致循环。一般来说,循环引用的集合/对象在序列化时(比如JSON序列化),如果不加以处理,会触发StackOverflowError异常。

所以Fastjson序列化时,会默认开启引用检测检测功能以检测重复或者循环引用。

检测到重复或者循环引用,则会出现如下类似的情况:

  1. {
  2. "averageErrSeconds":0,
  3. "averageRunSeconds":70.4,
  4. "day":"20210826",
  5. "equipName":"830",
  6. "errCount":0,
  7. "errDetails":[],
  8. "hour":"02",
  9. "runCount":15,
  10. "runDetails":[
  11. {"$ref":"$.result[0].runDetails[22]"},// 出现了重复引用标识
  12. {
  13. "begindate":"2021-08-26 02:03:55 903",
  14. "enddate":"2021-08-26 02:05:12 289",
  15. "equipdesc":"830堆垛机",
  16. "equipid":"4e4d5a0ee1d947d59040442f1c1ffc48",
  17. "equipnum":"830",
  18. "errtime":0,
  19. "etype":"CraneEquipment",
  20. "exetaskqty":0,
  21. "freetime":0,
  22. "id":"84E6F98A-BBA3-487D-841D-73E9713597EF",
  23. "runtime":76.386,
  24. "status":"RUN",
  25. "totaltime":0,
  26. "version":1
  27. },
  28. {
  29. "begindate":"2021-08-26 02:08:49 959",
  30. "enddate":"2021-08-26 02:10:24 022",
  31. "equipdesc":"830堆垛机",
  32. "equipid":"4e4d5a0ee1d947d59040442f1c1ffc48",
  33. "equipnum":"830",
  34. "errtime":0,
  35. "etype":"CraneEquipment",
  36. "exetaskqty":0,
  37. "freetime":0,
  38. "id":"D7F1E076-6B5B-4C34-A5AD-6B2527305710",
  39. "runtime":94.063,
  40. "status":"RUN",
  41. "totaltime":0,
  42. "version":1
  43. }
  44. ],
  45. "totalErrMillis":0,
  46. "totalErrSeconds":0,
  47. "totalRunMillis":1056156,
  48. "totalRunSeconds":1056
  49. }

这样的序列化结果,在某些情况下可能会导致意料之外的问题。比如,给前端返回的数据,如果出现重复或者循环引用,则可能前端现实异常。如下图所示的第一行数据,显示为空。
image.png
为了解决上述问题,可以关闭Fastjson默认开启的引用检测功能:

  1. JSON.toJSONString(respondObject, SerializerFeature.DisableCircularReferenceDetect);

尽管可以通过关闭引用检测来解决重复或者循环引用导致的序列化结果出现引用标识的问题,但是也应注意:

  • 在编码时,使用新对象为集合或对象赋值,而非使用同一对象;
  • 不要在多处引用同一个对象,这可以说是一种java编码规范,需要时刻注意;
  • 引用检测是FastJson提供的一种避免运行时异常的优良机制,如果为了避免在重复引用时显示$ref而关闭它,会有很大可能导致循环引用时发生StackOverflowError异常。这也是FastJson默认开启引用检测的原因。

引用标识:

  1. "$ref":".." //上一级
  2. "$ref":"@" //当前对象,也就是自引用
  3. "$ref":"$" //根对象
  4. "$ref":"$.children.0" //基于路径的引用,相当于root.getChildren().get(0)

顺序问题

关于json内部键值的顺序,在构造JSONObject时,可以通过以下两个方法来保证构造出来的json字符串按照添加顺序存储:

  1. // 方式一
  2. JSONObject jsonObject1 = new JSONObject(true);// ture=使用LinkHashMap构造
  3. jsonObject1.put("1", "a");
  4. jsonObject1.put("2", "b");
  5. // other put operation
  6. // 方式二
  7. LinkedHashMap<String,Object> jsonMap = new LinkedHashMap<>();
  8. jsonMap.put("1", "a");
  9. jsonMap.put("2", "b");
  10. // other put operation
  11. JSONObject jsonObject2 = new JSONObject(jsonMap);

但是,如果已经构造好的按照指定顺序存储的JSONObject,如果想要对其进行更新操作,比如插入更多的键值,那么可能会更改其顺序。

  1. LinkedHashMap<String,Object> hmMap = new LinkedHashMap<>();
  2. hmMap.putAll(jsonObject.getJSONObject(Dir.HM.name()));
  3. hmMap.put(Dir.RT.name(), "SUCCESS");
  4. hmMap.put(Dir.RC.name(), ResultCode.Success.code);
  5. jsonObject.put(Dir.HM.name(), hmMap);