JSON对象与字符串转换全攻略:序列化、反序列化与调试实战
发布时间:2026/10/1 17:44:58来源:尧图网络
最近一周我被三个同事问了同一类问题前端传过来的数据后端解析报错、后端返回的字符串前端取不到值、接口文档里明明写着JSON却怎么都拼不出一个合法对象。这些问题的本质都指向同一个基础动作——JSON对象与字符串之间的转换。在Web开发里这个动作几乎是每天都会碰到的基本功但越是基础的东西踩坑的人越多。JSON转换看起来就是两个函数的事序列化和反序列化。但要真正用明白你得知道不同语言里的差异、特殊值的处理、大数字精度、日期格式、工具调试技巧还有各种反序列化失败的排查思路。这篇文章我把这些年实际项目中积累的经验整理成一套完整的实操笔记覆盖JavaScript、Python、Java、C#四个主流场景也包含浏览器DevTools、Postman、JMeter这些调试工具的日常用法希望能帮前端、后端、测试甚至爬虫方向的朋友一次把这块打通。1. 先把基础掰扯清楚对象、JSON、JSON字符串1.1 三种概念一句话版很多人写代码写了好几年其实没完全分清“JSON”“JSON字符串”“JavaScript对象”三者的关系。我习惯用一个类比JSON是“装箱单的填写规范”JSON字符串是一张写好的“装箱单”而对象是包裹里的“实际货物”。JSON本身是一种数据交换格式规定了数据怎么写——哪些符号是合法的、字段名要不要加引号、数组怎么表示。它不隶属于任何编程语言虽然写法长得和JavaScript的对象字面量有点像。JSON字符串是符合JSON语法的文本本质是字符串也就是由字符组成的序列。它在网络传输、文件存储、数据库文本字段里出现时都以文本形式存在。对象是程序运行时内存里的数据结构比如JavaScript里的Object、Python里的dict、Java里的POJO、C#里的class实例。对象里可以装函数、日期、自定义类型等更丰富的结构。转换就是把“货物”按“装箱单规范”装成“单据”序列化或者反过来把“单据”还原成“货物”反序列化。1.2 为什么传输时必须转成字符串一个最常见的疑问是HTTP请求的时候我明明可以直接把对象放在请求体里为什么还要先转成JSON字符串原因在传输层的限制。HTTP请求和响应的body本质上是文本流或二进制流程序运行时的对象结构比如内存地址、函数闭包、循环引用无法直接跨进程跨语言传递。就像你寄快递不能把仓库里的整个货架直接塞进快递车必须按规则打包成能搬运的箱子。具体到实际开发里这几点尤其明显前端AJAX发送请求时XMLHttpRequest或fetch的body只能接收字符串、二进制数据等直接传对象会被隐式转成类似[object Object]的字符串后端根本没法解析。后端接收请求时框架会把JSON字符串反序列化成强类型对象这个过程如果字符串格式不对就会抛出各种解析异常。本地存储如localStorage只能存字符串要把对象存进去必须序列化。消息队列、Redis、数据库JSON字段也都是以JSON字符串形式存储的。所以“对象转字符串”和“字符串转对象”这两个方向是整个数据链路里最常用的两个动作。2. JavaScript开发者的日常JSON.parse与JSON.stringify2.1 JSON.parse字符串变对象的正确姿势JavaScript里把JSON字符串转成对象标准做法就是JSON.parse。基本用法一行代码const str {name:张三,age:18}; const obj JSON.parse(str); console.log(obj.name); // 张三就这么简单。但实际项目中parse报错是重灾区。最常见的几个坑第一个坑JSON字符串必须是严格JSON语法不能用单引号、不能有尾逗号、不能有注释。// 下面这些全部会报错 JSON.parse({name:张三}); // 单引号不行 JSON.parse({name:张三,}); // 尾逗号不行 JSON.parse({name:张三} /*注释*/); // 注释不行很多后端返回的数据里混进了换行、制表符、\r\nJSON.parse一般能自动处理空白字符但如果是肉眼可见的格式错乱比如截断了半个JSON、字段名没加引号解析必然失败。第二个坑JSON.parse无法解析函数、undefined、NaN、Infinity。JSON.parse({fn: function(){}}); // 报错 JSON.parse({v: undefined}); // 报错 JSON.parse({v: NaN}); // 报错这就是为什么后端返回JSON时字段值只能是字符串、数字、布尔值、null、数组、对象这几种类型。第三个坑解析失败会抛异常必须接住。项目里我见过无数次因为没包try/catch导致页面白屏的情况。任何来自外部输入的JSON字符串都不要赌它100%合法let data null; try { data JSON.parse(resp); } catch (e) { console.error(解析响应数据失败, e); // 这里可以走降级逻辑比如给用户一个友好的错误提示 }JSON.parse还支持第二个参数reviver可以在解析过程中对每个键值对做二次处理。比如把ISO日期字符串自动转成Date对象const result JSON.parse(str, (key, value) { if (typeof value string /^\d{4}-\d{2}-\d{2}T/.test(value)) { return new Date(value); } return value; });这个技巧在对接第三方接口时很实用但注意不要滥用避免每个字段都走一遍判断影响解析性能。2.2 JSON.stringify对象变字符串的正确姿势对象转JSON字符串用的是JSON.stringify。基础用法const obj { name: 张三, age: 18, tags: [前端, JSON] }; const str JSON.stringify(obj); // {name:张三,age:18,tags:[前端,JSON]}除了第一眼看到的基础功能JSON.stringify还有几个很实用的进阶参数很多人用了多年都没用上。第二个参数replacer可以过滤字段。如果你只想序列化对象里的部分字段可以传一个数组或函数const user { id: 1, name: 张三, password: 123456, email: ab.com }; // 只保留id和name JSON.stringify(user, [id, name]); // {id:1,name:张三} // 用函数做更复杂的过滤 JSON.stringify(user, (key, value) { if (key password) return undefined; // 返回undefined会跳过该字段 return value; });这个特性在清理敏感字段、减少传输体积时很管用。第三个参数space格式化输出。调试的时候想看结构清晰的JSON字符串传一个缩进即可JSON.stringify(obj, null, 2); // 输出带2个空格缩进的JSON这在console面板里打印复杂对象、生成接口文档示例、写JSON配置文件时都是神器。第四个隐藏能力如果对象定义了toJSON方法stringify会自动调用它。const obj { name: 张三, toJSON() { return { nickName: this.name, from: toJSON }; } }; JSON.stringify(obj); // {nickName:张三,from:toJSON}很多日期处理库就是靠这个钩子自定义序列化结果的。你自己写业务对象时也可以靠toJSON精确定义对外输出的结构而不是把所有内部字段都暴露出去。说到底有几个特殊值必须留意undefined、function、Symbol序列化时会被忽略在数组里会变成null。NaN、Infinity会被转成null。日期对象会调用toISOString()转成字符串。循环引用直接抛出TypeError。const obj { a: 1, fn() {}, b: undefined, c: Symbol(x) }; JSON.stringify(obj); // {a:1}fn、b、c 都没了 const circular {}; circular.self circular; JSON.stringify(circular); // TypeError: Converting circular structure to JSON大数字精度问题也是高频踩坑点。JavaScript里JSON.stringify遇到超过Number.MAX_SAFE_INTEGER的数字会丢失精度JSON.stringify({ id: 9007199254740993 }); // {id:9007199254740992}末尾的3丢了涉及ID、订单号这类超大数值时强烈建议后端先转成字符串再返回或者前端用库去处理BigInt场景。2.3 深拷贝神话JSON.parse(JSON.stringify(obj))为什么不能滥用const clone JSON.parse(JSON.stringify(obj))是前端圈流传很广的深拷贝写法。面试里能背出来但真正用到生产环境坑一个接一个。这个写法能工作的前提是对象结构简单只包含JSON兼容的数据类型。可现实是项目里的对象常常包含函数、Date、RegExp、Map、Set、循环引用这些在序列化过程中要么丢失、要么变形const original { name: 张三, date: new Date(), regex: /abc/g, map: new Map([[k, v]]), fn() { console.log(hi); } }; const clone JSON.parse(JSON.stringify(original)); // clone.date 变成了字符串 // clone.regex 变成了 {} // clone.map 变成了 {} // clone.fn 直接消失如果只想拷贝纯数据对象这个写法性能也不占优。现代浏览器和Node.js都原生支持structuredClone能正确处理Date、RegExp、Map、Set、ArrayBuffer还能处理循环引用const clone structuredClone(original);所以我的建议是深拷贝优先用structuredClone只有明确知道自己拷贝的是纯JSON数据时才用JSON.parse(JSON.stringify(obj))这条路。3. 真实项目里的常用转换场景3.1 对象数组去重JSON字符串当临时key实际开发里从后端拉回来的数组经常有重复项比如配置列表、标签列表去重是常见需求。对象数组去重不能直接用Set因为对象比较的是引用。一个容易上手且稳定的方案是用JSON.stringify把每个对象序列化成字符串作为唯一键const arr [ { id: 1, name: a }, { id: 2, name: b }, { id: 1, name: a } ]; const seen new Set(); const unique arr.filter(item { const key JSON.stringify(item); if (seen.has(key)) return false; seen.add(key); return true; }); // 结果保留两条重复项被过滤这个方案有局限性如果对象字段顺序不同比如{id:1, name:a}和{name:a, id:1}序列化出来的字符串是不同的会被误判为不同对象。稳妥做法是先用一个排序函数统一字段顺序或者干脆用稳定且明确的主键去重。另外如果对象里包含函数、undefined等JSON.stringify会跳过这些字段也可能导致去重判断偏差。所以这个方法适合纯数据对象不适合复杂对象。3.2 数组与字符串互转、逆序、大小写转换的联动JSON字符串处理过程中数组和字符串之间的互转也经常出现。Array.join和String.split是最常用的两个方法const arr [a, b, c]; const str arr.join(,); // a,b,c const back str.split(,); // [a, b, c]注意split的结果是字符串数组如果你要的是数字数组需要再map一层Number。JSON数组字符串也可以借用这个思路做临时处理但要小心元素本身含逗号的情况最好用一个不太可能出现在数据里的分隔符或者直接走JSON.parse。字符串大小写转换在JSON场景里也有用武之地。比如后端返回的字段是first_name前端要转成firstName除了专门写映射也可以用简单的字符串操作预处理。还有一个绕不开的场景给接口文档做示例JSON时字段名命名风格不统一camelCase vs snake_case写个小脚本用正则加toLowerCase/toUpperCase批量转换比手动改字段效率高得多。字符串逆序在普通业务里用得不多但在某些编码解码、日志分析、算法题场景里会碰到。C语言里写字符串逆序要手动操作字符数组和指针JavaScript里一行就能搞定const reversed str.split().reverse().join();如果你在刷题可能会遇到“给定一个仅由小写英文字母组成的字符串s找出所有删除该位置字符后能使剩余字符满足某种条件的下标”这类问题思路往往是先转字符数组逐位删除再拼回字符串判断本质还是字符数组与字符串的互转。3.3 金额、布尔值与null的边界处理对象转JSON后类型的边界往往容易被忽略。最常见的是金额浮点数在JSON传输和解析时可能产生精度问题比如0.10.2不等于0.3。我的建议是金额字段在接口层统一用字符串表示或者乘以100存成整数避免浮点误差const money 19.99; // 推荐 JSON.stringify({ price: money.toFixed(2) }); // 不推荐直接传浮点前后端加减乘除后容易出精度问题布尔值的坑也值得注意。JSON里只有true和false两个合法布尔值但有的后端会返回0/1或true/false字符串。前端解析时最好做一层统一转换比如const boolValue rawValue true || rawValue 1 || rawValue true || rawValue 1;null更是重灾区。反序列化后对象的字段可能不存在也可能是null还可能是空字符串。访问深层字段前最好用可选链?.和空值合并??做兜底避免“Cannot read properties of null”这类报错刷屏。对象合并与拷贝也是高频操作。展开运算符{...a, ...b}和Object.assign可以合并普通对象但都是浅拷贝嵌套对象修改时容易互相污染。4. 其他主流语言怎么实现JSON转换4.1 Pythonjson模块与自定义对象Python里的JSON转换主要靠标准库json。json.dumps把对象转字符串json.loads把字符串转对象import json data {name: 张三, age: 18} text json.dumps(data, ensure_asciiFalse) # {name: 张三, age: 18}ensure_asciiFalse 保证中文不转成 \uXXXX obj json.loads(text) # {name: 张三, age: 18}这里ensure_asciiFalse是我强烈建议加上的参数。否则Python默认会把中文转成\u5f20\u4e09这种Unicode转义序列虽然网络传输没问题但打印日志、落盘文件时非常影响可读性第三方接口对接时也可能被误认为数据损坏。Python的json.dumps还支持indent参数做格式化text json.dumps(data, ensure_asciiFalse, indent2)对自定义类对象默认是没法直接序列化的class User: def __init__(self, name, age): self.name name self.age age user User(张三, 18) json.dumps(user) # 报错Object of type User is not JSON serializable这时候可以给dumps传default参数自定义转换函数def user_to_dict(obj): if isinstance(obj, User): return {name: obj.name, age: obj.age} raise TypeError(f暂不支持序列化{type(obj)}) text json.dumps(user, defaultuser_to_dict, ensure_asciiFalse) # {name: 张三, age: 18}Python里还有一个高频场景是把ORM查询结果转成JSON。Django的QuerySet如果用json.dumps直接转多半会报错推荐先用list()转成字典列表再序列化from django.core import serializers objects list(SomeModel.objects.values(id, name)) text json.dumps(objects, ensure_asciiFalse)pandas的DataFrame也有自带的to_json方法import pandas as pd df pd.DataFrame({name: [张三, 李四], age: [18, 20]}) text df.to_json(orientrecords, force_asciiFalse)orientrecords是转成对象数组的形式后端接口返回列表数据时很常用。4.2 JavaJackson与GsonJava生态里JSON处理有两大主流库Jackson和Gson。Spring Boot默认内置Jackson所以大多数场景直接注入ObjectMapper即可import com.fasterxml.jackson.databind.ObjectMapper; ObjectMapper mapper new ObjectMapper(); // 对象转字符串 String json mapper.writeValueAsString(user); // 字符串转对象 User u mapper.readValue(json, User.class);Jackson的注解体系很成熟常用两个public class User { JsonProperty(user_name) private String userName; JsonIgnore private String password; }JsonProperty可以把字段名映射成下划线格式JsonIgnore可以在序列化和反序列化时忽略指定字段比如密码、内部缓存字段。Jackson反序列化报错信息很丰富最常见的是UnrecognizedPropertyExceptionJSON里有多余字段和MismatchedInputException类型不匹配。如果接口返回的字段比实体类多可以在类上配置忽略未知字段JsonIgnoreProperties(ignoreUnknown true) public class User { // ... }Gson的用法也类似Gson gson new Gson(); String json gson.toJson(user); User u gson.fromJson(json, User.class);Gson默认会序列化所有字段包括值为null的字段如果不想要null字段可以配置serializeNulls策略或使用Expose注解配合GsonBuilder.excludeFieldsWithoutExposeAnnotation()。Java项目里还有一个容易踩坑的地方用MyBatis Plus时有人会试图把QueryWrapper对象直接序列化传输这基本是行不通的QueryWrapper内部结构复杂且包含数据库条件信息不适合跨端传递。正确做法是把查询条件封装成一个DTO对象再走JSON序列化。4.3 C#System.Text.Json与Newtonsoft.JsonC#里现在有两套主流方案微软官方的System.Text.Json和社区老牌库Newtonsoft.Json也叫Json.NET。两者核心用法差不多using System.Text.Json; // 对象转字符串 string json JsonSerializer.Serialize(user); // 字符串转对象 User u JsonSerializer.DeserializeUser(json);Newtonsoft.Json的语法更老牌些using Newtonsoft.Json; string json JsonConvert.SerializeObject(user); User u JsonConvert.DeserializeObjectUser(json);System.Text.Json默认属性名和C#属性名保持一致PascalCase如果前端接口要求camelCase风格需要配置命名策略var options new JsonSerializerOptions { PropertyNamingPolicy JsonNamingPolicy.CamelCase }; string json JsonSerializer.Serialize(user, options);Newtonsoft.Json里也有对应的CamelCasePropertyNamesContractResolver。C#场景中反序列化数组或字典很常见ListUser users JsonSerializer.DeserializeListUser(json); Dictionarystring, string map JsonSerializer.DeserializeDictionarystring, string(json);有些做视觉或工业软件开发的朋友可能用C#对接Halcon这类视觉库检测结果往往需要输出给上层MES系统或前端页面。实测下来把检测结果定义成强类型对象比如检测时间、缺陷坐标、判定结果再序列化成JSON输出比手工拼接字符串靠谱得多字段不会被拼错类型也有编译期保障。4.4 跨语言传输的通用约定多语言之间传JSON最大的问题不是语法而是“同一份数据大家理解不一样”。几个高频冲突点字段命名风格前端习惯camelCase后端习惯snake_case。建议在接口文档里统一一种或基于字段映射框架自动转换。时间格式后端可能返回时间戳、ISO字符串、yyyy-MM-dd HH:mm:ss前端解析方式完全不同。推荐统一用ISO 8601字符串如2026-01-01T12:00:00Z各家语言都有标准解析方案。数字精度超大整数、高精度decimal统一用字符串表达避免精度丢失。null与空字段有的后端不返回null字段有的返回null有的返回空字符串。消费方需要统一兜底逻辑。字符编码HTTP响应头里的Content-Type要带charsetutf-8否则中文可能出现乱码。这些约定看起来都是小事但联调阶段大部分时间都耗在这上面。早点在接口文档里定好规则能省下大量排查时间。5. 用开发工具快速验证与排查JSON5.1 Chrome DevTools的实用技巧前端调试时Chrome DevTools是最常用的工具但很多人对它的JSON功能了解得不深。Network面板里查看某个请求的载荷时新版DevTools默认展示成格式化好的对象树。想复制原始JSON字符串可以点击右键选择“Copy”里的“Copy as fetch”生成的代码里带着完整的JSON body非常方便。Console面板里调试对象时JSON.stringify配合第三参数可以输出可读性极强的格式console.log(JSON.stringify(largeObject, null, 2));有些时候在Console里右键一个对象发现菜单里没有“Copy object”之类的选项或者复制出来是一段奇怪的字符串。这是因为Console展示的对象是实时引用的直接复制不一定能得到JSON格式。最稳妥的方式是用copy()这个内置函数copy(JSON.stringify(obj, null, 2));调用后字符串自动进剪贴板粘贴出来就是标准的JSON文本非常适合导出接口数据或者做Mock。很多人会遇到“载荷不能复制对象”的困惑其实是因为页面里的对象属于页面上下文DevTools控制台有需要切换到对应上下文之后才能操作。在Console左上角的下拉框把执行上下文切换成当前页面再来执行copy(JSON.stringify(obj))基本都能解决。5.2 Postman/Apifox接口调试中的JSON处理接口调试工具里Postman和Apifox都内置了JSON语法检查。请求体里如果JSON格式有问题编辑器会有明显报错提示这个功能能帮你避免把错误的JSON发给后端。Pre-request Script预请求脚本里也可以做JSON转换。比如你要根据环境变量动态生成请求体const payload { userId: pm.environment.get(userId), timestamp: Date.now() }; pm.environment.set(requestBody, JSON.stringify(payload));然后在Body里引用{{requestBody}}。这套思路很适合在测试环境自动构造动态请求参数。Apifox在“断言”和“后置操作”里也能对响应体做处理比如提取某个字段作为下个接口的入参底层也都是JSON.parse和JSON.stringify的组合。5.3 JMeterJSON Extractor 取值后怎么看取到的值JMeter做接口性能测试或联调时经常要用JSON Extractor从响应里提取变量。配置方式不复杂添加HTTP请求提取下一个接口需要的数据。在请求上右键“添加 → 后置处理器 → JSON Extractor”。填JSONPath表达式比如$.data.token。变量名称填token。配置完之后很多人不知道去哪看提取到的值。最简单的办法是添加一个“Debug Sampler”放在JSON Extractor后面运行后在“查看结果树”里选到Debug Sampler就能看到所有JMeter变量其中就包括token的值。在后续请求里引用变量时用${token}比如放在Authorization头里Authorization: Bearer ${token}JSONPath本身也有一些容易踩的坑。比如数组元素匹配用$..book[0]但如果你提取的字段在嵌套对象里路径写错就提取不到。建议在Debug Sampler里逐步验证路径别一步到位期望能取到值。6. 高频报错与排查技巧6.1 “failed to deserialize the JSON body into the target type”这类错误后端开发尤其容易遇到类似报错failed to deserialize the JSON body into the target type: input: missing field name这个报错信息常见于Rust的Axum等框架但背后的原因在所有语言里都一样收到JSON字符串里少了目标类型必需的字段。常见原因有三种前端传参时漏字段。前端构造对象时某属性为undefined被JSON.stringify序列化时直接丢弃了。字段名大小写不一致。前端传userName后端实体类期望username对不上。字段值类型不匹配。前端传字符串18后端期望数字类型反序列化失败。排查思路也有固定套路先在后端打日志把收到的原始body打印出来肉眼比对字段再看前端实际发送的请求体抓包工具或浏览器Network面板里能看到最终发出的JSON最后对着接口文档逐字段核对。这类报错千万别靠猜最快的办法是把请求体原样复制出来用在线JSON格式化工具校验一下合法性往往一眼就能发现问题。6.2 传输层面容易忽略的编码与字符问题JSON传输过程中中文乱码、特殊字符转义也常让人头疼。HTTP响应头里没有charsetutf-8时浏览器可能按默认编码解析导致中文显示乱码。后端返回JSON时建议明确设置Content-Type: application/json; charsetutf-8反过来前端给后端传JSON时如果URL没有做encode中文可能会变成不可读的字符。在axios或fetch里请求体里的中文一般会自动按UTF-8编码但如果你用的是application/x-www-form-urlencoded格式就需要手动encodeURIComponent。JSON里的特殊字符也必须转义双引号、反斜杠、换行符、回车符、制表符。如果字符串里包含这些字符序列化工具会自动处理但你要是手拼JSON字符串十有八九会在这个环节出错。还有一个容易被忽略的是Unicode转义。\uXXXX形式在JSON里是合法的但不同语言解析时可能表现不同。比如\u00e9é在大部分语言里都能正确还原但某些老旧的客户端却不支持。跨语言传值时尽量使用UTF-8原始字符避免使用复杂的Unicode转义序列。6.3 安全与性能的几条建议JSON转换在安全和性能上也有讲究尤其是高并发和涉及用户输入的场景。第一不要盲目信任外部JSON。反序列化前要做校验字段是否存在、类型是否正确、值是否在合法范围内。尤其是把JSON转成强类型对象再插入数据库的场景字段校验不过关很容易产生脏数据。第二解析超大JSON要注意性能。JSON.parse和json.loads都是全量解析遇到几十MB的JSON耗时和内存消耗都很可观。可以考虑流式解析方案如JavaScript的JSON.parse配合分段处理Python的ijson库按需读取关键字段。第三避免“JSON.parse(JSON.stringify(x))”当深拷贝用。前面说过这不仅有数据丢失风险性能也不如专门的深拷贝方案。浏览器里优先考虑structuredCloneNode.js后端也可以直接用。第四序列化时做字段裁剪。不要把整个Entity直接序列化返回前端尤其是包含密码、密钥、内部状态的对象。用DTO或toJSON钩子精确控制输出字段既安全又减小传输体积。7. 最后我个人的几条经验做了这么多年开发JSON转换几乎每天都在用我总结几条真正提升效率的心得。第一所有项目都统一字段命名规范并写进接口文档。我经历过的联调事故里一大半跟字段名对不上有关。前端用camelCase后端用snake_case只要提前约定好并做映射至少能减少一半沟通成本。第二序列化逻辑一定要可定制。不要图省事直接整个对象丢给序列化器该裁剪的字段要裁剪该重命名的要重命名。这在接口数据安全和传输性能上都很重要。第三所有解析动作都要包一层异常处理。这是血泪教训线上环境一个未捕获的JSON解析异常轻则接口返回500重则整个服务进程受影响。try/catch或者等价的异常捕获不能省。第四测试用例里一定要覆盖空值和边界值。空字符串、null、缺字段、超大数字、空对象数组这些情况看起来不会发生生产环境却经常冒出来。把这几个边界在测试里跑一遍能挡掉大量线上问题。最后分享一个小技巧当你对某个JSON结构不确定时先用格式化工具把它“看顺眼”再决定是解析还是构造。很多报错其实是在“结构没看清楚”的情况下就动手写代码导致的。JSON本身并不难难的是认真对待每一个字段、每一个边界唯有多实践多踩坑才能真正把这门基本功练扎实。
网站建设高端定制企业官网