【问题记录】使用 Fastjson 的 toJSONString 后莫名出现了 $ref...
1 前言
最近比较忙,在对接别的系统,然后昨天莫名发现一个问题,今天来记录一下,大致是处理 JSON 对象我们可能会用到 Fastjson,在序列化的时候,现象如下:
public static void main(String[] args) { // 商品单位信息 PackingUnitResDto packingUnitResDto = new PackingUnitResDto(); packingUnitResDto.setBaseUnit(true); packingUnitResDto.setUnitCode("pcs"); // 商品 sku SkuResDto skuResDto = new SkuResDto(); skuResDto.setPackingUnitList(Lists.newArrayList(packingUnitResDto)); // 商品 spu ItemResDto item = new ItemResDto(); item.setPackingUnitList(Lists.newArrayList(packingUnitResDto)); // sku 放进 spu 里 item.setSkuList(Lists.newArrayList(skuResDto)); // spu、sku的单位信息设置的是同一个对象 System.out.println(JSON.toJSONString(item)); }
如图,toJSONString 后出现了莫名的 ref啥的。
2 解决
查过资料后发现,通过fastjson将实体转化为json字符串时,在传输的数据中如果出现相同的对象,fastjson默认开启引用检测会将相同的对象写成引用的形式。
引用是通过"$ref"来表示的,引用分两种,循环引用和重复引用。
引用 | 描述 |
---|---|
"$ref":".." | 上一级 |
"$ref":"@" | 当前对象,也就是自引用 |
"$ref":"$" | 根对象 |
"$ref":"$.children.0" | 基于路径的引用,相当于 root.getChildren().get(0) |
循环引用:即A对象引用B对象,B对象又引用A对象,这种情况是要极力避免的,因为会导致堆栈溢出(StackOverflowError);
重复引用:上面的例子就是因为相同的单位对象出现在两个集合中,所以第二个集合中直接返回的是$ref。一般大家在写代码的过程中,如果出现$ref,通常应该是重复引用问题。
好啦,那么知道为什么后,我们看看如何解决:
2.1 局部关闭(建议)
使用SerializerFeature.DisableCircularReferenceDetect关闭循环引用:
System.out.println(JSON.toJSONString(item, SerializerFeature.DisableCircularReferenceDetect));
2.2 全局关闭(不建议)
可以在SpringBoot项目的json配置中将循环引用关闭。FastJson增加以下项:
static { // 全局配置关闭Fastjson循环引用,避免出现$ref JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask(); }
/** * FastJson配置类 **/ @AutoConfiguration public class FastJsonConfig { static { // 全局配置关闭Fastjson重复引用,避免出现$ref JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask(); } @Bean public HttpMessageConverters fastJsonHttpMessageConverters() { FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); List<MediaType> mediaTypes = new ArrayList<>(); mediaTypes.add(MediaType.APPLICATION_JSON); /****************************FastJsonConfig Start*****************************/ com.alibaba.fastjson.support.config.FastJsonConfig fastJsonConfig = new com.alibaba.fastjson.support.config.FastJsonConfig(); // 序列化配置 SerializeConfig serializeConfig = new SerializeConfig(); // BigInteger、Long转JSON精度丢失配置 serializeConfig.put(BigInteger.class, ToStringSerializer.instance); serializeConfig.put(Long.class, ToStringSerializer.instance); serializeConfig.put(Long.TYPE, ToStringSerializer.instance); // 序列化值为null的字段 fastJsonConfig.setSerializerFeatures( SerializerFeature.PrettyFormat , SerializerFeature.WriteNullListAsEmpty , SerializerFeature.WriteMapNullValue //, SerializerFeature.WriteNullStringAsEmpty //, SerializerFeature.WriteNullNumberAsZero //, SerializerFeature.WriteNullBooleanAsFalse ); fastJsonConfig.setSerializeConfig(serializeConfig); fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); fastJsonConfig.setCharset(StandardCharsets.UTF_8); /****************************FastJsonConfig End*****************************/ converter.setSupportedMediaTypes(mediaTypes); converter.setFastJsonConfig(fastJsonConfig); return new HttpMessageConverters(converter); } }
但是因为全局配置是在我们项目的基础jar包中配置的,改动基础jar包会有风险,会对前面所有的依赖项目产生影响。所以也不采用这种方式。
大家想想为什么Fastjson默认是开启这个功能的就知道原因了。如果全局关闭,性能也会有极大影响。所以不建议哈,并且影响面未知。