【问题记录】使用 Fastjson 的 toJSONString 后莫名出现了 $ref...

发布时间 2023-09-27 07:36:11作者: 酷酷-

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配置类
 *
 * @author wyb
 * @date 2023/01/01 00:00 周六
 **/
@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默认是开启这个功能的就知道原因了。如果全局关闭,性能也会有极大影响。所以不建议哈,并且影响面未知。