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

news2024/12/23 13:41:48

在这里插入图片描述

一、背景

在构建一个公共的批处理方法类的时候,在测试输出的时候,打印了" 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.coloradmin.cn/o/1633681.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL Binlog 闪回与分析

文章目录 前言1. 修改 event 实现闪回1.1 binlog 结构1.2 闪回案例1.3 方法总结 2. 解析文本闪回2.1 mysqlbinlog2.2 闪回案例2.3 方法总结 3. 在线订阅闪回3.1 mysql-replication3.2 binlog2sql3.3 方法总结 4. Binlog 分析方法4.1 分析场景4.2 辅助定位事务4.3 方法总结 5. 平…

性能监控之prometheus+grafana搭建

前言 Prometheus和Grafana是两个流行的开源工具&#xff0c;用于监控和可视化系统和应用程序的性能指标。它们通常一起使用&#xff0c;提供了强大的监控和数据可视化功能。 Prometheus Prometheus是一种开源的系统监控和警报工具包。它最初由SoundCloud开发&#xff0c;并于…

基于SSM+Jsp+Mysql的汽车租赁系统的设计与实现

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

Python爬虫(入门版)

1、爬虫是什么 简单的来说&#xff1a;就是用程序获取网络上数据。 2、爬虫的原理 如果要获取网络上数据&#xff0c;我们要给爬虫一个网址&#xff08;程序中通常叫URL&#xff09;&#xff0c;爬虫发送一个HTTP请求给目标网页的服务器&#xff0c;服务器返回数据给客户端&am…

Linux详解:进程等待

文章目录 进程等待等待的必要性进程等待的方法waitwaitpid获取子进程status阻塞等待 与 非阻塞等待 进程等待 等待的必要性 子进程退出&#xff0c;父进程不进行回收的话&#xff0c;就可能造成僵尸进程&#xff0c;进而造成内存泄露 如果进程进入了僵尸状态&#xff0c;kill…

宝塔面板安装教程(linux)

宝塔官网地址 宝塔官网linux安装地址 针对Ubuntu系统的安装命令&#xff1a; wget -O install.sh https://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh ed8484bec 安装过程中&#xff0c;中途会出现一个 Y&N ? 的选项&#xf…

李沐62_序列到序列学习seq2seq——自学笔记

"英&#xff0d;法”数据集来训练这个机器翻译模型。 !pip install --upgrade d2l0.17.5 #d2l需要更新import collections import math import torch from torch import nn from d2l import torch as d2l循环神经网络编码器。 我们使用了嵌入层&#xff08;embedding l…

【笔记1】从零开始做一个男头的流程(超级详细)

目录 大体 眼窝 鼻子 脖子 耳朵 嘴巴1 颧骨 嘴巴2 眼睛 头 开始细化 大体 眼窝 嘴巴 鼻子 大体 注意&#xff01;&#xff01;先整体后局部&#xff0c;一开始不要加太多的线&#xff0c;尽量先用最少的线调整出一个大体的结构。 1.准备好参考图&#xff0c;在…

2024年的Java版本选择?java 17 安装

文章目录 2024年的Java版本选择&#xff1f;java 1.8 和 java17 什么区别&#xff1f;java 17 安装windows 11安装java 17C:\Program Files\Common Files\Oracle\Java\javapath是什么 2024年的Java版本选择&#xff1f; 3年前&#xff0c;java 1.8是市场主流&#xff08;还有一…

Acrobat Pro DC 2023:专业PDF编辑软件,引领高效办公新时代

Acrobat Pro DC 2023是一款专为Mac和Windows用户设计的专业PDF编辑软件&#xff0c;凭借其强大的功能和卓越的性能&#xff0c;成为现代职场人士不可或缺的得力助手。 这款软件拥有出色的PDF编辑能力。用户不仅可以轻松地对PDF文档中的文字、图片和布局进行编辑和调整&#xf…

PyAudio安装!!解决使用pip install PyAudio安装报错问题

如果使用pip install PyAudio安装报错 一般建议选择本地安装 但是本人也是从网上找了很多资料&#xff0c;发现本地的wheel的网址打开没有文件了 然后我就用了这个方法&#xff0c;对于我的电脑是非常有效果的&#xff01;&#xff01; 如果指令装不上的话 PyAudio PyPI …

linux中git的使用

为什么要有git git相当于一个仓库可以让我们更好的去管理我们的代码&#xff0c;实现版本的控制&#xff0c;上传到云端仓库。有了git,就可以实现多人同时开发一个项目&#xff08;每个负责一部分代码&#xff0c;最后都上传到同一个仓库&#xff09;。 git github/gitee 的区…

Burp 指纹识别+OA弱口令爆破-BurpFingerPrint

简介 攻击过程中&#xff0c;我们通常会用浏览器访问一些资产&#xff0c;该BurpSuite插件实现被动指纹识别网站提取链接OA爆破&#xff0c;可帮助我们发现更多资产。 功能如下 下述功能会在2024年5月底完成&#xff0c;如果有更好的建议都可以提&#xff0c;然后再麻烦点个…

linux磁盘原理

在linux系统中&#xff0c;对磁盘进行管理与windows系统类似&#xff0c;都要先分区&#xff0c;格式化&#xff0c;创建文件系统&#xff0c;挂载目录&#xff0c;数据写入

【Unity动画系统】Animator组件的属性

介绍Animator组件的全部属性 Controller&#xff1a;动画控制器 Avatar&#xff1a;人物骨骼 Apply Root Motion&#xff1a;有一些动画片段自带位移&#xff0c;如果希望自带的位移应用在游戏对象上&#xff0c;那么就勾选&#xff1b;如果自己编写脚本&#xff0c;那么就不…

Milvus Cloud 向量数据库Reranker成本比较和使用场景

成本比较:向量检索 v.s. Cross-encoder Reranker v.s. 大模型生成 虽然 Reranker 的使用成本远高于单纯使用向量检索的成本,但它仍然比使用 LLM 为同等数量文档生成答案的成本要低。在 RAG 架构中,Reranker 可以筛选向量搜索的初步结果,丢弃掉与查询相关性低的文档,从而有…

使用webpack给大屏自适应插件autofit.js增加umd打包方式

最近有个大屏自适应的需求&#xff0c;而且想直接通过script标签来引入自适应的插件js&#xff0c;搜索相中了autofit.js&#xff0c;可惜不支持umd格式的引入&#xff0c;虽然也能直接copy源码&#xff0c;但是还是折腾下给它打包成umd格式的代码。 fork源码&#xff0c;克隆…

第10章 项目管理基础知识

一、项目概述 &#xff08;一&#xff09;项目 在既定的项目资源要求和约束下&#xff0c;为实现特定目标而相互联系的一次性活动&#xff08;资源任务&#xff09;。世界上没有两个完全相同的项目项目有资源约束&#xff0c;一定的目的&#xff0c;是一次性。 &#xff08;…

面试官:Docker和传统虚拟机有什么区别?

我有一个程序员朋友&#xff0c;他每年情人节都要送女朋友一台服务器。 他说&#xff1a;“谁不想在过节当天收到一台 4核8g 的服务器呢&#xff1f;” “万一对方不要&#xff0c;我还能留着自己用。” 给他一次过节的机会&#xff0c;他能把浪漫玩的明明白白。 所以今年情人…

APP上架APP Store因为苹果登录被拒,该如何解决

之前有一段时间 &#xff0c;我们的APP因为苹果登录被拒了几次。分享出来&#xff0c;希望对大家有所帮助。 主要有两种被拒理由&#xff1a; 没有登录/苹果登录。登录按钮设计不符合标准。 这其实是很小的一件事情。但是就是这么小的事情&#xff0c;我们在这上面栽了几次跟…