JSON.toJSONString() 输出 “$ref“:“$[0]“问题解决及原因分析

在这里插入图片描述

一、背景

在构建一个公共的批处理方法类的时候,在测试输出的时候,打印了" r e f " : " ref":" ref":"[0][0]"的内容,这让我比较疑惑。不由得继续了下去…

二、问题分析

首先,我们需要明确 ,在使用诸如Java的序列化库(如Jackson、Gson或Fastjson等)将数据转换为JSON字符串时,JSON.toJSONString(map<String,String>) 调用中可能出现 “ r e f " : " ref":" ref":"[0][0]” 的原因。在JSON序列化过程中,“ r e f " : " ref":" ref":"[0][0]” 这类引用标记通常表示对象中存在循环引用,即一个对象直接或间接地引用了自己。JSON序列化库在检测到这种循环引用时,会尝试使用引用来避免无限递归,并节省内存。

对于简单的 Map<String, String> 类型,通常不应该出现循环引用,因为键值对本身不包含对其他键值对的引用。因此,这个问题可能是在其他部分的代码或序列化库的实现中产生的。

2.1 问题示例

假设我们有一个简单场景,其中Map中的值是一个自引用的类实例,这会导致序列化时出现$ref。


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class, 
    property = "id")
class SelfReferencingObject {
    int id;
    SelfReferencingObject selfRef;

    public SelfReferencingObject(int id) {
        this.id = id;
    }

    public void setSelfRef(SelfReferencingObject ref) {
        this.selfRef = ref;
    }
}

public class Demo {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        SelfReferencingObject obj1 = new SelfReferencingObject(1);
        SelfReferencingObject obj2 = new SelfReferencingObject(2);
        
        // 创建循环引用
        obj1.setSelfRef(obj1);
        obj2.setSelfRef(obj2);
        
        Map<String, SelfReferencingObject> map = new HashMap<>();
        map.put("obj1", obj1);
        map.put("obj2", obj2);
        
        String json = mapper.writeValueAsString(map);
        System.out.println(json);
    }
}

上述代码在运行时,将会输出类似于以下内容,其中包含了$ref来表示循环引用:

{
“obj1”: {
“id”: 1,
“@ref”: “KaTeX parse error: Expected 'EOF', got '}' at position 8: [0]" }̲, "obj2": { …[1]”
}
}

在这里插入图片描述

三、原因解析

3.1 循环引用

如果Map中的值直接或间接地引用了Map本身或其他位于Map中的对象,形成了一个闭环,序列化时为了防止无限循环和堆栈溢出,序列化库会使用$ref来标记已处理过的对象,避免重复输出。

3.2 重复引用

即使没有循环引用,但如果多个键值对引用了相同的对象实例,一些序列化库也会使用$ref来优化输出,表示这些位置引用的是同一个对象。

四、解决方案

4.1. 禁用循环检测(不推荐,仅作演示)

大多数序列化库提供了配置选项来禁用循环引用检测,但这可能会导致其他问题,如无限循环序列化。

ObjectMapper mapper = new ObjectMapper();
mapper.disable(com.fasterxml.jackson.databind.deser.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.enable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DURABLE_OBJECT_IDS);
String json = mapper.writeValueAsString(map);
...

注意:此方法可能不会直接解决问题,且可能导致无限循环或其它错误,实际应用中应谨慎。

4.2. 自定义序列化策略

你可以通过实现自定义的序列化器或采用库提供的注解等方式,控制特定对象或类的序列化行为,避免$ref的产生。

// 使用Jackson的@JsonIdentityInfo注解解决循环引用
// 上面的SelfReferencingObject类已经添加了@JsonIdentityInfo注解

// 序列化代码保持不变
使用@JsonIdentityInfo后,输出的JSON会为重复的对象生成唯一ID,而不是直接使用$ref。

4.3. 手动处理引用

在序列化前,检查并打破潜在的循环引用,比如将引用替换为ID或者浅拷贝对象以切断循环链。

public class Demo {
    public static void main(String[] args) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        
        SelfReferencingObject obj1 = new SelfReferencingObject(1);
        SelfReferencingObject obj2 = new SelfReferencingObject(2);
        
        // 防止循环引用,这里不设置selfRef
        
        Map<String, SelfReferencingObject> map = new HashMap<>();
        map.put("obj1", obj1);
        map.put("obj2", obj2);
        
        String json = mapper.writeValueAsString(map);
        System.out.println(json);
    }
}

这个例子中,我们直接不设置selfRef,从而避免了循环引用。

4.4. 使用特定库的功能

某些库如Jackson提供了@JsonIdentityInfo注解来处理循环引用问题,自动为重复的对象生成ID引用。

已在示例1中展示了如何使用Jackson的@JsonIdentityInfo注解处理循环引用。

或者使用JSON.toJSONString(Object object, SerializerFeature… features)方法,并传入SerializerFeature.DisableCircularReferenceDetect特性来禁用循环引用检测。


   String jsonString = JSON.toJSONString(users, SerializerFeature.DisableCircularReferenceDetect);
   

在这里插入图片描述

五、补充知识点

在Java中,对象之间的循环引用、重复引用、自引用或相互引用,通常在代码层面直观体现为对象的属性互相指向对方。下面通过示例来具体展示这些引用方式:

5.1. 循环引用

当两个或多个对象互相持有对方的引用,形成一个闭环,这就是循环引用。


class Person {
    String name;
    Person friend;

    Person(String name) {
        this.name = name;
    }

    void setFriend(Person friend) {
        this.friend = friend;
        // 这里设置朋友的friend为自己,形成循环引用
        friend.setFriend(this);
    }
}

public class Main {
    public static void main(String[] args) {
        Person alice = new Person("Alice");
        Person bob = new Person("Bob");
        alice.setFriend(bob); // 设置Alice的朋友是Bob
    }
}

在这个例子中,alice的朋友是bob,而bob的朋友又被设置为alice,形成了循环引用。

5.2. 重复引用

如果多个变量或数据结构引用同一个对象实例,就是重复引用。

class Book {
    String title;
    
    Book(String title) {
        this.title = title;
    }
}

public class Main {
    public static void main(String[] args) {
        Book popularBook = new Book("Popular Title");
        List<Book> library = new ArrayList<>();
        library.add(popularBook);
        library.add(popularBook); // 同一个Book实例被添加两次,形成重复引用
    }
}

这里,popularBook这个Book实例被library列表重复引用了两次。

5.3. 自引用

自引用指的是对象的一个属性直接或间接地引用自身。

class Node {
    String data;
    Node next; // 可能指向自己,形成自引用

    Node(String data) {
        this.data = data;
    }

    void setNext(Node next) {
        this.next = next;
    }
}

public class Main {
    public static void main(String[] args) {
        Node node = new Node("Node Data");
        // 形成自引用
        node.setNext(node);
    }
}

在Node类的例子中,next属性可以设置为指向自己,形成自引用。

总结

在解决这个问题时,关键是要找到循环引用的来源。这可能需要你深入检查代码和序列化库的实现。一旦找到循环引用的来源,你就可以采取适当的措施来避免它,例如修改代码逻辑或自定义序列化过程。如果问题是由序列化库引起的,更新到最新版本或寻找替代库可能是一个解决方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/592354.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《苍穹外卖》前端课程知识点记录

一、VUE基础知识 基于脚手架创建前端工程 1. 环境要求 安装node.js&#xff1a;Node.js安装与配置&#xff08;详细步骤&#xff09;_nodejs安装及环境配置-CSDN博客查看node和npm的版本号 安装Vue CLI&#xff1a;Vue.js安装与创建默认项目&#xff08;详细步骤&#xff09;…

DHCPv4_CLIENT_ALLOCATING_06: 发送DHCPDISCOVER消息 - 在没有收到DHCPOFFER消息时超时并重新发送

测试目的&#xff1a; 验证DOIP客户端在未收到DHCP服务器的DHCOFFER消息时&#xff0c;能够正确地超时并重传DHCPDISCOVER消息。 描述&#xff1a; 在DOIP网络环境中&#xff0c;当客户端&#xff08;DUT&#xff09;启动并尝试获取IP地址时&#xff0c;它首先发送DHCPDISCO…

IoTDB 入门教程 基础篇⑨——TsFile导入导出工具

文章目录 一、前文二、准备2.1 准备导出服务器2.2 准备导入服务器 三、导出3.1 导出命令3.2 执行命令3.3 tsfile文件 四、导入4.1 上传tsfile文件4.2 导入命令4.3 执行命令 五、查询六、参考 一、前文 IoTDB入门教程——导读 数据库备份与迁移是数据库运维中的核心任务&#xf…

获取淘宝商品销量数据接口

淘宝爬虫商品销量数据采集通常涉及以下几个步骤&#xff1a; 1、确定采集目标&#xff1a;需要明确要采集的商品类别、筛选条件&#xff08;如天猫、价格区间&#xff09;、销量和金额等数据。例如&#xff0c;如果您想了解“小鱼零食”的销量和金额&#xff0c;您需要设定好价…

设计模式之前端控制器模式

想象一下&#xff0c;你的Java Web应用是个交响乐团&#xff0c;每个功能模块是乐手&#xff0c;而用户请求就像是一首首待演绎的曲目。在这场音乐盛宴中&#xff0c;谁来保证演出的流畅与协调&#xff1f;答案就是——前端控制器模式&#xff01;它如同乐队的指挥&#xff0c;…

用LangChain打造一个可以管理日程的智能助手

存储设计定义工具创建llm提示词模板创建Agent执行总结 众所周知&#xff0c;GPT可以认为是一个离线的软件的&#xff0c;对于一些实时性有要求的功能是完全不行&#xff0c;比如实时信息检索&#xff0c;再比如我们今天要实现个一个日程管理的功能&#xff0c;这个功能你纯依赖…

短视频素材去哪里找免费?短视频素材从哪儿下载?

在这个数字内容为王的时代&#xff0c;视频已经成为沟通信息和吸引观众的强大工具。无论是在市场营销、教育还是娱乐领域&#xff0c;高质量的视频素材都是制作引人注目内容的关键。以下列出的网站提供多样的视频素材&#xff0c;帮助您增强视觉叙述&#xff0c;并在竞争激烈的…

2022 HITCON -- fourchain-kernel

前言 很久没碰内核利用相关的东西了&#xff0c;这个题目都调了我两天&#xff08;&#xff1a;所以还是得熟能生巧啊 题目分析 内核版本&#xff1a;v5.10&#xff0c;所以不存在 cg 隔离、可以使用 userfaultfdkaslr、smap、smep 开启CONFIG_SLAB_FREELIST_RANDOM 和 CONF…

Java项目:基于SSM框架实现的学院党员管理系统高校党员管理系统(ssm+B/S架构+源码+数据库+毕业论文+开题)

一、项目简介 本项目是一套基于SSM框架实现的学院党员管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、功能齐…

2024年3月Scratch图形化编程等级考试(二级)真题试卷

2024年3月Scratch图形化编程等级考试&#xff08;二级&#xff09;真题试卷 选择题 第 1 题 默认小猫角色&#xff0c;Scratch运行程序后&#xff0c;舞台上出现的图形是&#xff1f;&#xff08; &#xff09; A. B. C. D. 第 2 题 下列哪个Scratch选项可以使虫子移到…

Dynamics 365: 从0到1了解如何创建Custom API(1) - 在Power Apps中创建

今天介绍一下如果创建Custom API&#xff0c;我们首先需要知道它和action有什么区别&#xff0c;什么时候使用Custom API或者Action? Custom API和Action的区别 Create your own messages (Microsoft Dataverse) - Power Apps | Microsoft Learn 什么时候使用Custom API或者…

3.11设计模式——Visitor 访问者模式(行为型)

意图 表示一个作用于某对象结构中的各元素的操作。它允许在不改变各元素的类的前提下定义作用于这些元素的新操作。 结构 Visitor&#xff08;访问者&#xff09;为该对象结构中ConcreteElement&#xff08;具体元素&#xff09;的每一个类声明一个Visit操作&#xff0c;该操…

将java项目上传到GitHub步骤

文章目录 GitHub 作用github如何修改默认分支为master手把手教你把项目上传github上github怎么删除仓库或项目执行到push时报错的解决办法github怎么修改仓库语言 GitHub 作用 GitHub 是一个存放软件代码的网站&#xff0c;主要用于软件开发者存储和管理其项目源代码&#xff…

C++入门系列-类对象模型this指针

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 类对象模型 如何计算类对象的大小 class A { public:void printA(){cout << _a << endl;} private:char _a; }; 算算看&#xff0c;这个类的大小是多少 我们知道…

Unity 性能优化之Profiler窗口(二)怎么看懂这个分析器

提示&#xff1a;仅供参考&#xff0c;有误之处&#xff0c;麻烦大佬指出&#xff0c;不胜感激&#xff01; 文章目录 前言一、Profiler打开方式二、Profile简介添加没有的模块1.点击Profiler Modules&#xff08;分析器模块&#xff09;2.勾选GPU即可 自定义模块1.点击Profile…

JS 笔记9 认识JavaScript

相关内容&#xff1a;JS对象、属性、常用事件处理过程、运算符、if...else、for、…… <script type"text/javascript"></script> type属性用来指定MIME(Multipurpose Internet Mail Extension)类型&#xff0c;主要是告诉浏览器目前使用的是哪一种Scri…

SpringBoot实现Config下自动关联.xml、.properties配置信息的实例教程

本篇文章主要讲解在SpringBoot实现Config下自动关联.xml、.properties配置信息的实例教程。 日期&#xff1a;2024年5月4日 作者&#xff1a;任聪聪 .properties文件调用方法 步骤一、打开我们的 .properties 创建一个demo参数如下图&#xff1a; 步骤二、创建一个config的包&…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-8.2-链接脚本

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

用python画一个正八边形

1 问题 使用turtle库的turtle.fd()函数和turtle.seth()函数绘制一个边长100的正八边形。 2 方法 1、利用for循环解决如何画出图形中相同的八条边的问题。 2、再利用turtle.fd()函数和turtle.seth()函数画出完整的图形。 代码清单 1 import turtleturtle.pensize(2)d0for i in r…

“科技让广告更精彩”四川迈瑞斯文化传媒有限公司 行业领先的一站式媒体采购供应平台

国际数字影像产业园与园区企业一同推动数字影像技术的创新与发展&#xff0c;为数字影像产业注入新的活力。其中&#xff0c;四川迈瑞斯文化传媒有限公司&#xff08;906&#xff09;作为数字媒体行业的优秀企业&#xff0c;坚持“科技让广告更精彩”的理念&#xff0c;致力于为…
最新文章