Fastjson序列化空Map出现key为$ref的键值对的问题

起因

业务中创建了一个存储表格数据的对象,如下

@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来引用已经序列化过的相同对象,避免重复输出 在上述问题中,SheetDataDtoSheetColumnInfoSheetRowInfo对象内部的 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

参考

  • 通义灵码

  • fastjson SerializerFeature详解[1]

参考资料
[1]

fastjson SerializerFeature详解: https://blog.csdn.net/u010246789/article/details/52539576


原文始发于微信公众号(程序员段宝):Fastjson序列化空Map出现key为$ref的键值对的问题

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/272497.html

(0)
木子先生的头像木子先生

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!