JavaScript中Map与Set及循环引用对象的JSON序列化教程

JavaScript中Map与Set及循环引用对象的JSON序列化教程

本教程旨在解决javascript中包含`map`、`set`以及循环引用等复杂数据结构的对象的json序列化问题。我们将探讨`json.stringify()`直接处理这些结构时遇到的挑战,特别是循环引用导致的堆栈溢出错误。核心解决方案是利用javascript对象的`tojson()`方法,通过自定义序列化逻辑,将非标准或循环引用的数据结构转换为可被`json.stringify()`正确处理的普通javascript对象或基本类型,从而实现复杂对象的完整且友好的json输出。

理解JSON序列化的挑战

在JavaScript中,将复杂对象转换为JSON字符串是常见的操作,通常使用JSON.stringify()方法。然而,当对象中包含Map、Set等ES6引入的数据结构,或者存在循环引用时,JSON.stringify()的默认行为会遇到障碍。

1. Map和Set的默认序列化问题

JSON.stringify()在处理Map和Set实例时,并不会将其内部元素直接序列化为JSON数组或对象。例如,Map会被序列化为空对象{},而Set则会被序列化为空对象{}或一个带有[Set]标识的非详细表示(取决于环境的console.log行为,而非实际JSON序列化)。这意味着,如果直接尝试序列化包含Map或Set的对象,这些数据结构内部的实际数据将丢失。

2. 循环引用导致的堆溢出

更严重的问题是循环引用。在图结构(如示例中的Graph类)中,节点之间通常存在相互引用,例如节点A指向节点C,同时节点C也指向节点A。当JSON.stringify()尝试序列化一个包含循环引用的对象时,它会陷入无限递归,因为它试图遍历并序列化一个永无止境的引用链。这会导致RangeError: Maximum call stack size exceeded错误,即堆栈溢出。

考虑以下图结构的代码示例:

立即学习“Java免费学习笔记(深入)”;

class Node {  constructor(value) {    this.value = value;    this.adjacents = new Set(); // 存储相邻节点,可能包含循环引用  }  addAdjacent(node) {    this.adjacents.add(node);  }}class Graph {  constructor(directed = false) {    this.nodes = new Map(); // 存储所有节点    this.directed = directed;  }  addVertex(value) {    if (this.nodes.has(value)) {      return this.nodes.get(value);    }    const vertex = new Node(value);    this.nodes.set(value, vertex);    return vertex;  }  addEdge(src, dest) {    let srcNode = this.nodes.get(src);    if (!srcNode) srcNode = this.addVertex(src);    let destNode = this.nodes.get(dest);    if (!destNode) destNode = this.addVertex(dest);    srcNode.addAdjacent(destNode);    if (this.directed === false) {      destNode.addAdjacent(srcNode); // 无向图导致循环引用    }  }}const g1 = new Graph();g1.addVertex("a");g1.addVertex("b");g1.addEdge("a", "c"); // a -> c, c -> a (因为是无向图)// 尝试直接序列化会导致错误或信息不完整// console.log(JSON.stringify(g1, null, 2));

直接对g1调用JSON.stringify(),会因为Node对象中的adjacents Set包含Node对象本身,以及Graph中的nodes Map包含Node对象,且节点间存在循环引用(例如a与c互为邻接),从而引发堆栈溢出错误。

解决方案:利用 toJSON() 方法

JavaScript提供了一个强大的机制来解决自定义对象的JSON序列化问题:toJSON()方法。如果一个对象拥有toJSON()方法,JSON.stringify()在序列化该对象时,会优先调用这个方法,并使用其返回值进行序列化,而不是直接序列化原始对象。这为我们提供了自定义序列化逻辑的入口。

通过在类中实现toJSON()方法,我们可以:

将Map和Set等非标准数据结构转换为普通数组或对象。将循环引用的对象替换为它们的标识符(如value属性),从而打破循环。

实现自定义序列化:修改 Node 和 Graph 类

为了正确序列化Graph对象,我们需要在Node和Graph类中分别实现toJSON()方法。

1. 改造 Node 类

Node类中的adjacents是一个Set,其中包含其他Node对象的引用。为了打破循环引用并正确序列化,我们需要将Set转换为一个包含相邻节点值的普通数组。

class Node {  constructor(value) {    this.value = value;    this.adjacents = new Set();  }  addAdjacent(node) {    this.adjacents.add(node);  }  // 实现 toJSON 方法  toJSON() {    return {      value: this.value,      // 将 adjacents Set 转换为一个包含相邻节点值的数组      // 这打破了循环引用,因为我们只存储值而不是完整的Node对象      adjacents: [...this.adjacents].map(({ value }) => value),    };  }}

在Node的toJSON()方法中:

我们返回一个新的对象,包含value属性。adjacents属性被设置为一个新数组。[…this.adjacents]将Set转换为一个包含Node对象的数组。.map(({ value }) => value)进一步遍历这个数组,只提取每个相邻Node的value属性。这样做既解决了Set的序列化问题,又通过只保留节点标识符(而非完整节点对象)打破了潜在的循环引用。

2. 改造 Graph 类

Graph类中的nodes是一个Map,其键是节点值,值是Node对象。我们需要将这个Map转换为一个普通的JavaScript对象,其中键是节点值,值是经过Node.toJSON()处理后的节点表示。

class Graph {  constructor(directed = false) {    this.nodes = new Map();    this.directed = directed;  }  addVertex(value) {    if (this.nodes.has(value)) {      return this.nodes.get(value);    }    const vertex = new Node(value);    this.nodes.set(value, vertex);    return vertex;  }  addEdge(src, dest) {    let srcNode = this.nodes.get(src);    if (!srcNode) srcNode = this.addVertex(src);    let destNode = this.nodes.get(dest);    if (!destNode) destNode = this.addVertex(dest);    srcNode.addAdjacent(destNode);    if (this.directed === false) {      destNode.addAdjacent(srcNode);    }  }  // 实现 toJSON 方法  toJSON() {    return {      directed: this.directed,      // 将 nodes Map 转换为一个普通对象      // Object.fromEntries() 将 Map 的 [key, value] 对转换为 { key: value }      // 由于 Map 的值是 Node 对象,它们会递归调用自身的 toJSON 方法      nodes: Object.fromEntries(this.nodes),    };  }}

在Graph的toJSON()方法中:

我们返回一个新对象,包含directed属性。nodes属性被设置为一个新对象,通过Object.fromEntries(this.nodes)将Map转换为一个普通对象。关键在于,Object.fromEntries()在处理Map的值(即Node对象)时,JSON.stringify()会递归地查找这些Node对象上的toJSON()方法并调用它,从而确保每个节点都被正确序列化为我们期望的格式。

完整示例与输出

现在,结合修改后的Node和Graph类,我们可以安全地序列化Graph对象并获得清晰的JSON输出。

class Node {  constructor(value) {    this.value = value;    this.adjacents = new Set();  }  addAdjacent(node) {    this.adjacents.add(node);  }  toJSON() {    return {      value: this.value,      adjacents: [...this.adjacents].map(({ value }) => value),    };  }}class Graph {  constructor(directed = false) {    this.nodes = new Map();    this.directed = directed;  }  addVertex(value) {    if (this.nodes.has(value)) {      return this.nodes.get(value);    }    const vertex = new Node(value);    this.nodes.set(value, vertex);    return vertex;  }  addEdge(src, dest) {    let srcNode = this.nodes.get(src);    if (!srcNode) srcNode = this.addVertex(src);    let destNode = this.nodes.get(dest);    if (!destNode) destNode = this.addVertex(dest);    srcNode.addAdjacent(destNode);    if (this.directed === false) {      destNode.addAdjacent(srcNode);    }  }  toJSON() {    return {      directed: this.directed,      nodes: Object.fromEntries(this.nodes),    };  }}const g1 = new Graph();g1.addVertex("a");g1.addVertex("b");g1.addEdge("a", "c"); // a -> c, c -> a (因为是无向图)console.log(JSON.stringify(g1, null, 2));

输出结果:

{  "directed": false,  "nodes": {    "a": {      "value": "a",      "adjacents": [        "c"      ]    },    "b": {      "value": "b",      "adjacents": []    },    "c": {      "value": "c",      "adjacents": [        "a"      ]    }  }}

现在,Graph对象被成功序列化为一个可读性强、结构清晰的JSON字符串。每个节点的adjacents列表都显示了其相邻节点的值,Map结构也被正确转换为对象,并且没有出现堆栈溢出错误。

注意事项与总结

toJSON()的优先级:JSON.stringify()在序列化对象时会优先查找并调用对象的toJSON()方法。这是自定义序列化行为的关键。打破循环引用:在设计toJSON()方法时,对于可能导致循环引用的属性(如图中的adjacents),务必将其转换为基本类型值或标识符,而不是包含完整对象的引用。数据结构转换:Map和Set需要显式地转换为可序列化的数据结构,如普通对象或数组。Object.fromEntries()和展开运算符…是常用的工具递归调用:JSON.stringify()在处理一个包含自定义toJSON()方法的对象时,如果该对象内部又包含其他自定义toJSON()方法的对象,它会递归地调用这些内部对象的toJSON()方法,从而实现层层序列化。可逆性:请注意,通过toJSON()方法进行的序列化不一定是可逆的。为了反序列化(即从JSON字符串重建原始JavaScript对象),通常需要实现一个对应的反序列化函数,该函数将根据JSON结构手动重建Map、Set以及建立正确的循环引用。

通过有效地利用toJSON()方法,开发者可以完全控制复杂JavaScript对象的JSON序列化过程,确保输出的JSON既完整又符合预期,同时避免常见的序列化陷阱。

以上就是JavaScript中Map与Set及循环引用对象的JSON序列化教程的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1533228.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
JavaScript 对象数组的灵活重构:以属性值作为新键的转换技巧
上一篇 2025年12月21日 00:24:11
掌握嵌套if语句:避免常见陷阱与优化实践
下一篇 2025年12月21日 00:24:18

相关推荐

  • 如何设置抖音推送人群年龄?抖音怎么设置推送人群

    在抖音这个庞大的流量生态中,如何让内容精准抵达目标用户,是每位创作者和广告投放者关注的核心问题。本文将详细解析如何设置抖音推送人群的年龄范围,帮助你提升内容的曝光效率与吸引力。 一、掌握抖音用户年龄结构 首先,了解平台整体的年龄分布至关重要。根据抖音官方发布的数据,其核心用户群体集中在18至35岁之…

    2026年8月27日
    100
  • ps如何挽救曝光不足的灰暗图片

    ps如何挽救曝光不足的灰暗图片ps如何挽救曝光不足的灰暗图片ps如何挽救曝光不足的灰暗图片ps如何挽救曝光不足的灰暗图片

    曝光不足的灰暗照片常常令人感到惋惜,但有了 photoshop(简称 ps),我们完全可以让这些影像重获新生。 首先,将需要处理的图片导入 Photoshop。进入操作界面后,点击顶部菜单栏中的“图像”,选择“调整”子菜单下的“曲线”功能。这一步是改善欠曝图像的核心操作之一。在弹出的曲线调节面板中,…

    2026年8月27日 用户投稿
    200
  • Swoole的协程(Coroutine)是什么?如何工作?

    swoole的协程是一种高效的并发编程模型,允许在单个线程内并行执行多个任务。1)协程通过协作式调度工作,主动让出执行权,提高资源利用率和性能。2)协程适合处理io密集型任务,需注意长耗时任务和共享资源管理。 Swoole的协程(Coroutine)是什么?如何工作? Swoole的协程是一种高效的…

    2026年8月27日
    000
  • Win7电脑底下一排图标没了怎么办?Win7电脑底下一排图标没了解决

    Win7电脑底下一排图标没了怎么办?Win7电脑底下一排图标没了解决Win7电脑底下一排图标没了怎么办?Win7电脑底下一排图标没了解决Win7电脑底下一排图标没了怎么办?Win7电脑底下一排图标没了解决Win7电脑底下一排图标没了怎么办?Win7电脑底下一排图标没了解决

    win7电脑任务栏图标消失怎么办?在日常使用电脑时,任务栏是我们进行多种操作的重要工具,比如启动程序、调整音量或网络设置等。但有时会遇到win7电脑底部的任务栏图标突然不见了的情况,这该怎么办呢?别担心,下面将为大家介绍几种解决方法。 Win7电脑任务栏图标消失的解决方法 方法一:任务栏被误设为隐藏…

    2026年8月27日 用户投稿
    000
  • 智能写作检测怎么规避_GPTZero检测原理与应对策略

    要规避AI检测,需让文本呈现人类写作的多样性与不确定性。GPTZero等工具依赖分析文本的“困惑度”和“突发性”,AI因用词规整、句式单一、缺乏情感易被识别。人类写作则具备高低起伏的节奏、个性化表达和真实情感体验。为降低检测风险,应主动打破模式化表达:灵活变换句式长短,增加词汇丰富性,使用比喻、排比…

    2026年8月27日
    100
  • 轻松集成OpenTelemetry:告别繁琐配置,拥抱高效监控!

    在构建复杂的分布式系统时,监控和追踪变得至关重要。但是,手动配置和集成各种监控工具往往是一个令人头疼的过程。OpenTelemetry旨在通过提供一套标准化的API和SDK来简化这一过程。 open-telemetry/opentelemetry 这个 Composer 元包,可以帮助你快速上手 O…

    用户投稿 2026年8月27日
    000
  • 带货新手快速入门 + AI 无人直播智能加持:轻松打造爆款直播间

    新手直播带货需先打好选品与定位基础,理解平台规则和“人货场”逻辑,再借助AI提升效率;AI可辅助内容生成、无人直播和短视频引流,但无法替代真人情感互动,应采用“人机协作”模式;通过OBS、TTS、虚拟数字人等工具实现AI直播,结合数据分析与用户思维持续优化,避免选品失误、内容单调、违规等问题,最终实…

    2026年8月27日
    000
  • 代码风格不一致?NextcloudCodingStandard帮你统一PHP代码风格

    在多人协作的 PHP 项目中,代码风格不一致是一个令人头疼的问题。不同的开发者可能有不同的编码习惯,导致代码可读性降低,维护成本增加。Nextcloud Coding Standard 旨在提供一套统一的代码风格规范,并结合 PHP CS Fixer 工具,可以自动格式化代码,解决代码风格不一致的问…

    用户投稿 2026年8月27日
    000
  • 华硕X870/X870E主板惊艳亮相《影之刃零》试玩会

    华硕X870/X870E主板惊艳亮相《影之刃零》试玩会华硕X870/X870E主板惊艳亮相《影之刃零》试玩会华硕X870/X870E主板惊艳亮相《影之刃零》试玩会华硕X870/X870E主板惊艳亮相《影之刃零》试玩会

    7月26日至27日,国产武侠风格动作游戏《影之刃零》在北京首钢园举办了首次大型线下试玩活动,吸引了数千名玩家齐聚一堂,共赴这场“江湖论剑”。作为知名电竞品牌,rog玩家国度不仅为现场提供了大量高性能硬件支持,更重磅展出了两款备受关注的x870/x870e主板——兼具萌趣与性能的华硕rog x870 …

    2026年8月27日 用户投稿
    100
  • 在若依框架中如何找到和配置MyBatis依赖?

    在若依框架(ruoyi-vue)中配置和查找mybatis依赖是开发过程中常见的问题,尤其对于新手开发者。本文将详细说明如何在若依框架中找到并引入mybatis依赖。 在若依框架的项目结构中,MyBatis依赖通常是通过Maven管理的,具体会在项目的pom.xml文件中定义。根据提供的信息,我们可…

    2026年8月27日
    000
  • Laravel API中的错误处理和返回格式规范

    在 laravel 中实现错误处理和规范 api 返回格式的步骤包括:1) 使用 laravel 内置的异常处理机制捕获和处理异常;2) 定义统一的返回格式结构,包含 success、data 和 message 字段;3) 在关键业务逻辑中使用 try-catch 块处理特定异常;4) 利用 ap…

    2026年8月27日
    000
  • 流光号虚拟主播直播带货指南(附详细教程+配套工具资源)

    流光号虚拟主播直播带货是一种高效且具未来感的数字营销新范式,其核心在于通过定制化虚拟形象、精细化内容脚本、技术融合与数据迭代,实现24小时不间断、品牌高度一致的低成本运营,解决真人主播成本高、状态波动、时间受限等痛点,借助新奇感吸引流量,并通过智能互动与情感化内容提升转化率,最终依托数据分析持续优化…

    2026年8月27日
    300
  • 告别传统API的烦恼:如何使用IbexaGraphQL打造高效灵活的数据接口

    可以通过一下地址学习composer:学习地址 传统API的困境与现代需求 作为一名开发者,你一定遇到过这样的场景:前端需要展示用户列表和每个用户的最新三篇文章,但后端提供的restful api却只有 /users 和 /posts 两个独立接口。于是,你不得不先请求 /users 获取用户id,…

    用户投稿 2026年8月27日
    000
  • 告别PHP阻塞等待:GuzzlePromises助你实现高效异步编程,优化复杂任务处理

    可以通过一下地址学习composer:学习地址 传统PHP的“等待之痛”:当你的应用被外部服务拖慢 想象一下,你正在构建一个php后台应用,其中一个核心功能是为用户生成一个聚合报告。这个报告的数据来源非常分散: 用户画像数据:来自内部的用户服务API。订单历史记录:来自另一个内部的订单服务API。实…

    用户投稿 2026年8月27日
    000
  • 我的世界神奇宝贝大师球获取攻略

    我的世界神奇宝贝大师球获取攻略我的世界神奇宝贝大师球获取攻略我的世界神奇宝贝大师球获取攻略我的世界神奇宝贝大师球获取攻略

    在我的世界神奇宝贝模组中,大师球是一种非常实用的道具,可以用来捕捉各种精灵小宠物,成功捕捉后便能将其驯服并为自己所用。那么,如何获得神奇宝贝大师球呢?这么实用的道具当然要多准备几个。大师球可以通过合成方式获取,需要制作多个部件,包括盖子和铁质结构部分,需要用到果实和铁锭等材料,同时还需要使用工作台和…

    2026年8月27日 用户投稿
    000
  • 幻彩号虚拟形象电商实战指南(附保姆级教程+工具资源库)

    幻彩号虚拟形象电商是将数字分身转化为商品的新兴商业模式。核心路径为:先定位虚拟形象风格与受众,再通过Vroid Studio、Blender等工具创建2D或3D模型,结合Live2D实现动态表现;随后生产短视频、直播等内容赋予形象“生命”,并通过B站、抖音、小红书等平台矩阵传播,结合NFT、虚拟商品…

    2026年8月27日
    400
  • Laravel与Livewire实现实时前端

    使用laravel和livewire实现实时前端可以通过以下步骤实现:1. 安装和配置livewire,使用composer安装并在视图中添加必要的样式和脚本;2. 创建livewire组件,使用命令行工具生成组件文件,并在其中定义属性和方法;3. 在laravel视图中使用livewire组件,实…

    2026年8月27日
    000
  • Laravel Telescope调试工具安装和使用

    laravel telescope的安装与使用步骤如下:1. 运行composer require laravel/telescope,2. 执行php artisan telescope:install,3. 发布配置文件php artisan vendor:publish –prov…

    2026年8月27日
    100
  • 美图秀秀如何去除图片上的文字_美图秀秀文字去除技巧

    1、使用消除笔可智能填充文字区域,通过涂抹目标位置并自动匹配周围纹理实现去字;2、克隆图章工具需手动采样并复制空白区域覆盖文字,适合复杂背景;3、裁剪功能适用于边缘文字,通过调整画面范围直接删除含字部分;4、贴纸或滤镜可用于遮盖残留文字,提升整体视觉效果。 如果您在使用美图秀秀处理图片时,希望去掉图…

    2026年8月27日
    100
  • 如何优雅地管理PHP异步操作:使用Composer引入GuzzleHttp/Promises

    Composer在线学习地址:学习地址 告别“回调地狱”:PHP异步操作的痛点 你是否曾遇到这样的场景:你的php应用需要从多个外部服务获取数据,或者执行一些耗时的后台任务。如果这些操作都是同步进行的,那么用户就得眼睁睁地看着页面转圈,直到所有操作完成。这不仅严重影响了用户体验,也浪费了服务器资源。…

    用户投稿 2026年8月27日
    000

发表回复

登录后才能评论
关注微信