起因
业务中创建了一个存储表格数据的对象,如下
@Data
public class SheetDataDto {
private List<SheetColumnInfo> columns;
private List<SheetRowInfo> rows;
private Map<String, Object> meta;
@Data
public static class SheetColumnInfo {
private String field;
private String title;
private String typeName;
private Map<String, Object> meta;
public SheetColumnInfo() {
this.meta = MapUtil.empty();
}
}
@Data
public static class SheetRowInfo {
private List<CellInfo> cells;
private Integer createId;
private Map<String, Object> meta;
public SheetRowInfo() {
this.cells = new ArrayList<>();
this.meta = MapUtil.empty();
}
public SheetRowInfo(Integer createId) {
this.createId = createId;
this.cells = new ArrayList<>();
this.meta = MapUtil.empty();
}
}
@Data
public static class CellInfo {
private String field;
private String value;
private Map<String, Object> meta;
public CellInfo() {
this.meta = MapUtil.empty();
}
}
public SheetDataDto() {
this.columns = new ArrayList<>();
this.rows = new ArrayList<>();
this.meta = MapUtil.empty();
}
}
该类包含嵌套结构,并且由于各个meta
属性在构造时需要为空,所以用了MapUtil.empty()
直接赋值给了meta
构造一个新对象,并对属性赋值,对象内容如下
SheetDataDto(columns=[SheetDataDto.SheetColumnInfo(field=jsm2dr, title=Name, typeName=string, meta={}), SheetDataDto.SheetColumnInfo(field=gadh9i, title=Notes, typeName=string, meta={}), SheetDataDto.SheetColumnInfo(field=xq1n1n, title=Status, typeName=string, meta={})], rows=[SheetDataDto.SheetRowInfo(cells=[], createId=1, meta={}), SheetDataDto.SheetRowInfo(cells=[], createId=1, meta={}), SheetDataDto.SheetRowInfo(cells=[], createId=1, meta={})], meta={})
但是在使用Fastjson对该对象进行序列化时,各个meta
会被序列化为 "meta":{"$ref":"$.data.columns[0].meta"}
,可是这个meta
本身就是一个空Map
{
"meta": { "$ref": "$.columns[0].meta" },
"rows": [
{ "meta": { "$ref": "$.columns[0].meta" }, "cells": [], "createId": 1 },
{ "meta": { "$ref": "$.columns[0].meta" }, "cells": [], "createId": 1 },
{ "meta": { "$ref": "$.columns[0].meta" }, "cells": [], "createId": 1 }
],
"columns": [
{
"meta": {},
"field": "jsm2dr",
"title": "Name",
"typeName": "string"
},
{
"meta": { "$ref": "$.columns[0].meta" },
"field": "gadh9i",
"title": "Notes",
"typeName": "string"
},
{
"meta": { "$ref": "$.columns[0].meta" },
"field": "xq1n1n",
"title": "Status",
"typeName": "string"
}
]
}
分析
Fastjson 在序列化过程中检测到循环引用(即对象结构中存在相互引用),为了防止无限递归和栈溢出,Fastjson 会自动启用循环引用检测,并使用$ref
来引用已经序列化过的相同对象,避免重复输出
在上述问题中,SheetDataDto
、SheetColumnInfo
和 SheetRowInfo
对象内部的 meta
属性虽然都为空,但MapUtil.empty()
是一个静态的空Map,三个对象的meta
其实都是同一个对象。当 Fastjson 序列化这些对象时,由于它们的 meta
属性都为空且互相引用,为了避免重复输出空对象,Fastjson 选择了使用 $ref
引用来替代直接输出空 meta 对象
解决
1、消除循环引用
将this.meta = MapUtil.empty();
替换为this.meta = new HashMap<>();
,让所有的meta
都是互不相关的空Map
2、设置 SerializerFeature
使用 SerializerFeature.DisableCircularReferenceDetect
特性可以禁用 Fastjson 的循环引用检测功能
JSON.toJSONString(sheet, SerializerFeature.DisableCircularReferenceDetect);
警告: 禁用循环引用检测可能会导致无限递归或栈溢出问题,如果对象结构中确实存在循环引用,请谨慎使用此方法
3、使用 JSON.toJSONStringZ()
Fastjson 提供了一个 toJSONStringZ()
方法,它会在序列化时忽略空对象
JSON.toJSONStringZ(sheet);
注意: toJSONStringZ() 方法可能会改变原始序列化的结果,因为它会忽略所有空对象,而不仅仅是引起循环引用的空对象,并且该方法已被标记为deprecated