2026/10/14 23:14:51

JSON SyntaxError at position 134 的前端排查与根因分析

JSON SyntaxError at position 134 的前端排查与根因分析 先说让我烦躁的那个瞬间接口明明返回了 200网络面板里也看得到数据前端却红字一片SyntaxError: Expected , or } after property value in JSON at position 134 (line 1 column 135)。我赌你看到这个报错的第一反应是去翻接口文档第二反应是复制 JSON 到某个在线解析器里然后盯着某个插件里的一堆转义符发呆。这个报错太典型了典型到几乎所有写前端的人都遇过但很多人并没有真正读懂它。它在告诉你的是你拿到的那一坨字符串里在第 134 个字符附近JSON 解析器期待一个,或}结果等来了一个它不认识的家伙。这通常不是说你少了逗号而是说逗号后面接了个不该接的东西或者数据在传输过程中被截断了、被污染了。每次看到这种报错我的习惯是先不要急着改代码按照一套固定的排查链路来走一遍多数时候几分钟就能定位到根因。这篇文章就把我平时对付这个报错的完整流程和踩坑经验整理出来从报错的真正含义到不同场景下的根因再到怎么从工程上彻底避免一次说清楚。1. 先读懂这行报错position 134 到底在说什么这个报错信息看着像是英文翻译的锅其实拆开看并不难理解。SyntaxError: Expected , or } after property value in JSON at position 134 (line 1 column 135)关键点是这几块Expected , or } after property value这是在告诉你JSON 解析器在处理一个属性值的收尾阶段。正常情况下一个属性值后面要么跟个逗号表示后面还有属性要么跟个右花括号表示对象结束了。结果两者都不是所以它懵了。position 134这是字符级别的位置索引从 0 开始计数。也就是说第 134 个字符就是触发错误的那一个。(line 1 column 135)大部分报错出现在第 1 行因为它处理的是完整的、已经被拆成单行的字符串。column 135 和 position 134 其实指向同一个地方只是 column 从 1 开始。有一个特别容易忽略的细节这个位置是 JSON 解析器的内部计数它基于的是字符串本身的字节或字符索引而不是你肉眼在浏览器控制台里看到的光标位置。如果 JSON 里有中文字符一个中文字符在 JavaScript 的字符串里算一个位置但在某些老版本工具里可能算两个这就可能导致你数来数去都数不准。我自己的经验是不用纠结手数到第 134 个字符把这个位置当作一个大致区域来用就够了。真正需要做的是拿到原始字符串切出报错位置前后一小段看一眼那附近是什么形状。比如用JSON.stringify(jsonString.slice(120, 150))打印出来这里的转义输出能让你看到不可见字符比如空格、换行、制表符甚至空字符串里藏着的\u0000之类的不可见 Unicode。再强调一句大部分人第一次遇到这个报错的反应是JSON 文件/字符串写错了少了个逗号。没错这是最常见的成因但绝对不唯一。我后面会列出几种我排查过程中遇到的真实情况每一种的表现都不太一样但报错信息完全相同。2. 为什么 JSON 字符串里会出现多余逗号和花括号常见根因拆解报错信息只会告诉你解析失败不会告诉你数据是怎么变成这样的。过去几年里我因为这个问题调试过不少次逐步总结出这么几类高频根因供查阅对照。2.1 服务端拼 JSON 字符串时写多了尾逗号这是最教科书的成因。有些熟悉 JavaScript 的开发者会习惯性地写出这样的代码// 错误的拼法多见于后端或中间层 const user { name: 张三, age: 18, // 这里多了一个逗号 }在 JavaScript 的对象字面量里对象最后一个属性后面写个逗号是合法的ES5 之后都支持 trailing comma甚至很多 ESLint 规则还推荐这么做。但 JSON 格式严格禁止尾逗号。如果服务端某段代码是用字符串拼接的方式来拼 JSON或者经过模板引擎、日志系统转换后再返回很容易把这个尾逗号带进最终输出。排查方法很简单复制响应体到任意 JSON 校验工具里它会明确提示第几行第几列有尾逗号。但如果你不想依赖在线工具也可以直接在 Node 环境里用JSON.parse去试报错信息和浏览器里完全一致。另一个容易踩的坑是服务端框架在返回响应时可能经过了多层中间件比如某个日志插件把响应体做了一次字符串拼接不小心在原有 JSON 后面加了点东西。这种问题从浏览器网络面板里看响应体可能是一张正常的 JSON但如果用Raw标签页查看就能看到末尾多出来的内容。2.2 响应被截断属性值后面直接断了这个成因隐蔽得多。假设服务端返回的是一个很大的 JSON比如一个带分页的大数组或者某个详情接口里嵌套了几层数组和对象。如果响应在写入阶段被截断网络问题、服务端压缩中间件 bug、负载均衡配置错误、反向代理的超时设置那么客户端拿到的字符串会在某个位置戛然而止。截断后的 JSON 往往长这样{ status: 0, data: { list: [ { id: 1, title: JSON解析看到没title属性的值没结束对象没闭合list后面的]也没出来。这时候解析器在读取到title: JSON解析之后会期待字符串继续但如果此时字符串恰好结束它同样会报错。不过这个报错位置通常会指向字符串的末尾附近而不是固定在某一行某列。还有一种情况是代理或压缩模块做了一半比如响应体是 gzip 压缩的但 Content-Encoding 头写的是 gzip实际返回内容却是原始 JSON。浏览器通常能识别一部分但在企业内网或者自研网关里经常会出现被解压到一半的数据。这种数据即便用肉眼在 Preview 标签页看也是乱七八糟的但很多人习惯直接看 Preview就漏过了 Raw 里的关键信息。2.3 数字、布尔值、null 后面误拼接了内容有时候 JSON 本身开头正常但内部某个值的后面出了问题。最经典的是{ code: 0undefined, msg: ok }这种通常是后端在渲染模板时出了问题比如模板引擎没转义把一个undefined或者一个方法的返回值直接拼到了 JSON 字符串里。还有一种情况是真的把两个 JSON 拼在一起了{a:1} {b:2}注意这种不是合法的 JSON合法的 JSON 只能有一个根对象或数组。解析器读到第一个}之后发现后面又来了一个{就会在第一个}附近报错。排查这类问题最有效的不是盯代码而是把响应体保存成文件用命令行工具或者脚本把字符串逐字节做一次字符可见化。我自己一般写成这样const fs require(fs); const raw fs.readFileSync(api_response.bin, utf8); console.log(JSON.stringify(raw.slice(120, 150)));这样打出来之后如果那个位置附近有undefined、NaN、[object Object]之类的字符串一眼就能看出来。2.4 引号没有转义属性值里的引号把结构打乱了这是新手容易踩、老手也偶尔翻车的另一类问题。{ content: 她说你好 time: 2025-01-01 12:00:00 }这个 JSON 的content值里包含中文引号全角引号其实没问题但如果里面是半角引号那就完蛋了。字符串内部的会提前终结整个字符串导致后续的内容全部错位。以报错位置为例如果content的值内部包含解析器会认为字符串在第一个内部处就结束了然后继续期望后面是,或}。实际上后面跟的是别的内容那就触发Expected , or }。这种问题的根因有两个方向一是数据源本身写了裸引号二是后端序列化的时候没有做字符转义或者用了某些不规范的序列化方式比如手写字符串拼接或者用数组 join 来拼。老实说手写 JSON 序列化的代码见过不少绝大部分最后都是在引号上出问题。2.5 不可见字符混入 JSONBOM、零宽空格、换行符最后这类最容易让人崩溃因为肉眼看不出任何异常。有一个提前量 BOMByte Order Mark字符它在文件开头以\uFEFF的形式存在。如果你用fs.readFileSync读取一个带 BOM 的 UTF-8 文件然后在没有去掉 BOM 的情况下拼接进 JSON 响应那么 JSON 解析器在第一个字符位置就会报错。还有一类是零宽空格\u200B或换行符乱了。尤其当你从某些文本编辑器复制富文本内容时编辑器可能会在复制时加入一堆不可见控制字符。这些字符如果混进 JSON 的属性值里倒还好只要不破坏结构但如果混到了花括号附近、逗号附近那解析器会认为这是非法字符。排查的时候记得打开编辑器的显示符号功能或者直接在控制台里打印出字符编码[...jsonString].map((c, i) ({ index: i, char: c, code: c.charCodeAt(0) }));这样列出来以后任何一个异常字符都会原形毕露。2.6 多层 JSON 嵌套过大浏览器/Node 自带解析器版本差异导致报错位置偏移严格说这不是根因而是一个容易让人在排查时误入歧途的干扰项。JSON 标准规定的解析规则是稳定的不同引擎对同一个非法 JSON 抛出的报错信息可能略有差异。比如你手写的在线解析工具可能指向position 134Node 的铁定也指向这个位置但一些老旧的 Java 或 PHP 序列化库可能把位置计算成 135 或 136。这不是解析器推断错了而是它们对 Unicode 字符的计位方式不一样。如果你在某个特殊网关里拿到的报错位置和浏览器不一致别慌先以浏览器/Node 的为准用JSON.parse在同一份字符串上跑一遍。3. 一步步排查从网络面板到原始字符串再到可控复现下面这是我个人的标准排查流程每次遇到这个报错就按这套走基本能快速定位到问题所在的层级。3.1 第一步不要盯着 Preview去看 Raw浏览器开发者工具的 Network 面板会做一次尝试 Parse JSON的预览这个预览一旦失败会显示一团乱码或者直接空白非常干扰判断。正确做法是切到 Raw 标签页把原始响应体复制下来存成一个.json文件或.txt文件。这一步很关键因为有些工具在复制时会帮你格式化、高亮不知不觉就修正了一些问题导致你排了个寂寞。拿到原始文本后第一时间用JSON.parse复现一下报错const fs require(fs); const raw fs.readFileSync(response.txt, utf8); try { JSON.parse(raw); } catch (e) { console.log(e.message); console.log(报错位置附近, JSON.stringify(raw.slice(120, 150))); }这样在本地复现之后后面的一切分析和调试都建立在同一份可靠的原始数据上不容易被工具误导。3.2 第二步判定是结构性问题还是字符性问题拿到报错位置之后切出一段字符来看这里通常会出现两种结果一种是报错位置附近出现了undefined、NaN、[object Object]、true、false、数字后面直接跟着字母等半路杀出的内容。这多半是序列化端或模板引擎拼错了东西。另一种是报错位置附近看起来完全正常比如刚好是一个对象属性值的末尾。这时候就要怀疑是不是标签闭合问题比如某个数组结束了、对象结束了但外层的容器还没结束解析器期望一个]或}结果来的是别的。还需要留意报错位置与整个字符串长度的关系。如果报错位置接近字符串末尾那大概率是响应被截断了可以看一下字符串最后几个字符是什么正常 JSON 应该是}或]结尾如果最后是或者逗号那就是截断问题。3.3 第三步分段二分法缩小范围如果报错位置不是在末尾而是中间偏后直接用二分法来排查。我常用的办法是把字符串一切为二分别做合法性校验虽然单独的半段 JSON 本身可能不完整但至少能判断出某个区间附近有没有明显的语法损坏。更实操的做法是把报错位置前后各取 20 个字符打印出来const start Math.max(0, 134 - 20); const end Math.min(raw.length, 134 20); console.log(前段, JSON.stringify(raw.slice(start, 134))); console.log(后段, JSON.stringify(raw.slice(134, end)));这个操作可以快速确认报错字符本身的 ASCII 码比如打出来是0x0A换行符或0x00空字符那就直接定位到不可见字符的方向了。3.4 第四步还原数据生成链路看是哪一层出的鬼排除了格式彻底损坏之后就要往上游看。通常要确认这么几层第一层服务端代码本身。它返回的是框架自动序列化的结果还是开发者在代码里手拼的 JSON 字符串第二层中间层。比如网关、日志插件、调试代理、Mock 工具是否有改写响应体的逻辑第三层网络传输层。是否经过了压缩、解压有没有超时截断的可能第四层前端代码。是不是在response对象上做了二次拼接、替换、模板渲染然后才交给JSON.parse的我自己碰到最多的是第四层尤其在工程里到处是包装响应的代码时。比如有人写了个handleResponse里面做了data.replace(//g, )或者做了data }之类的补全操作。这些看起来是在修复数据的代码反而会引入新的问题。3.5 第五步最小化复现定位到具体嫌疑代码后不要在原工程里盲改。最稳妥的做法是把响应体原始字符串写死在一个测试文件里然后用一个最小化的脚本去调用你准备使用 JSON 解析的逻辑逐步验证你的修改方案。比如把原来的JSON.parse(raw)改成先做一次清洗再解析然后对比清洗前和清洗后的解析结果。这样能确认改法确实有效再去动业务代码也不迟。4. 从工具和代码层面做防御解析前清洗与安全兜底问题能一次性修复当然好但根治这个报错的思路从来不是祈祷服务端永远返回合法 JSON而是让前端在解析前有一个合理的防御机制。毕竟你控制不了所有上游。4.1 封装一个安全的 JSON 解析函数这是我最基本的建议。无论项目里有多少个地方用到JSON.parse统一走一个包装函数至少能避免每次报错后代码里出现一堆 try/catch 的变体。函数大概长这样function safeParse(input, fallback null, errorHandler null) { if (typeof input ! string) { return fallback; } // 去掉 BOM let str input.charCodeAt(0) 0xFEFF ? input.slice(1) : input; try { return JSON.parse(str); } catch (err) { if (errorHandler) { errorHandler(err, str); } return fallback; } }关键点在于fallback给了业务方一个明确的这次没拿到数据的响应而errorHandler可以拿到原始的错误对象和原始字符串方便在问题发生时第一时间定位日志。我在实际项目中还会把str截断后塞进日志避免把几兆的响应全打出来。4.2 在拦截器/网络层处理响应体如果你用的是类 axios 的网络库可以在响应拦截器里统一判断响应的 Content-Type 和数据结构然后决定是否进行 JSON 解析。很多项目直接把response.data当成对象来用如果某个接口因为网关问题返回了纯文本整个调用链都崩了。我建议在拦截器里写一个白名单或黑名单逻辑如果response.data本身已经是对象就不重复解析如果是字符串才走safeParse。同时把解析失败的日志统一上报方便后面做数据质量监控。4.3 服务端你能够控制的那一端也别偷懒如果你的权限能覆盖服务端有几件事值得坚持优先使用框架自带的 JSON 序列化方法比如res.json()不要手拼 JSON 字符串。如果历史代码里确实有手拼 JSON 的场景改造时可以先在返回前做一次校验校验不通过则返回 500 或者空对象。对特别大的响应体考虑配置统一的响应压缩和缓冲策略降低被网关截断的概率。日志系统在采集响应体时注意别在 JSON 末尾追加任何内容。// 服务端伪代码返回前做一次合法性检查 const raw JSON.stringify(payload); try { JSON.parse(raw); } catch (e) { // 记录日志返回固定错误结构 return res.status(500).json({ code: 500, msg: response invalid }); } return res.status(200).send(raw);这会让非法响应在到达客户端前就暴露出来而不是等前端解析时才发现。4.4 前端兜底重要接口要降级而不是崩溃对付这类解析错误比修好它更关键的思路是别被它拖垮。重要的接口解析失败时页面应该给出一个明确的加载失败状态而不是白屏或者无限 loading。我习惯性的做法是解析失败时先尝试用原始字符串里的部分数据做兜底渲染。比如列表页的接口挂了但列表数据块里也许还能提取出一部分能用的数据详情页的接口挂了至少可以展示错误码和错误信息而不是整个页面没内容。5. 那些年我踩过的坑报错信息之外的隐形陷阱这个报错表面上看很简单但结合我在不同项目里的实际经历有几个隐形坑值得单独列出来。5.1 控制台打印对象时你以为看到的是最新值其实不是很多人排查时会在控制台里console.log(response.data)看到的是已经展开的对象。但这个对象是浏览器在展开时才去读取属性值如果你打印的是一个后续被拦截器修改过的引用控制台展示的可能不是报错发生那一刻的值。更稳妥的是每次打印时都JSON.stringify(response.data)把当时的快照记录下来不要打印引用类型。5.2 在线校验工具会自动修复慎用某知名在线 JSON 校验工具会在你粘贴无效 JSON 时自动帮你把尾逗号删掉、把裸引号补上然后提示校验通过。这种过度智能的工具会直接掩盖真实问题。我的建议是尽量用本地脚本、Node 环境的JSON.parse或者离线校验工具来做复现。因为它们是严格解析不会偷偷帮你纠错。真要使用在线工具眼睛也要盯着原始输入的文本而不是看它美化后的输出。5.3 某些代理工具/浏览器插件会改写响应开发环境里你可能开着浏览器代理、接口 Mock 插件、自动化测试工具这些都可能改写响应体。有一次我排查了一个下午最后发现是代理工具在 JSON 后面追加了一个注释节点导致前端解析失败。这类问题有三个特征报错位置极靠近末尾、线上环境完全正常、只有挂代理的本地才触发。遇到这种情况建议先关掉全部浏览器插件和调试代理再用无痕模式验证一次。5.4 JSON 嵌套太深与解析器的位置偏移刚才提过不同解析器可能对位置计数有差异。还有一个相关问题是当 JSON 嵌套很深比如超过 20 层某些老旧的序列化工具会截断或递归爆栈导致你拿到的字符串根本不是完整的 JSON而是写到一半的产物。这种截断在某些极端嵌套场景下报错位置看起来完全随机。我的建议是如果某个接口的响应结构复杂到十几层嵌套服务端就应该考虑扁平化输出或者分段加载而不是把一个巨大的嵌套对象一口气塞给前端。这不仅是为了解析稳定也是为了数据传输效率和代码可读性。5.5 别忽视字符编码问题如果响应头里声明的是charsetgbk但实际内容按 UTF-8 编码那么浏览器在解码时会出现乱码。乱码里可能包含0x80之类的字节这也会让 JSON 解析器报出类似错误。检查响应头、确认编码一致性是个常被忽略的步骤。6. 从根上减少这类报错工程规范和监控机制最后聊一点工程层面的心得。代码写得再好如果全靠人肉排查成本依然太高。这个报错在团队里反复出现的话说明缺的不是某一次修复而是一套规范和监控。6.1 统一序列化与反序列化入口前后端都约定好JSON 的生成只通过框架自带的序列化方法JSON 的解析只通过封装好的safeParse。只要统一入口出了问题就只会出现在这一个封装函数里排查范围瞬间缩小。接口返回的结构也尽量统一比如{ code, msg, data }三层不要在某个接口单独穿个数组根。数组根在解析上没问题但会让很多通用逻辑比如解析失败降级变得别扭。6.2 用内容校验而非肉眼审查在服务端加一个简单的 JSON Schema 校验或者至少用JSON.parse做一次完整性校验是投入产出比很高的做法。越是核心的接口越值得这么做。如果团队愿意多做一些可以在 CI 阶段跑一个所有 mock 数据必须能通过 JSON.parse的检查这能拦截掉相当一部分低级的尾逗号和裸引号问题。6.3 把解析错误作为监控指标我现在习惯在safeParse的errorHandler里把错误上报到监控平台并带上接口路径、报错位置、原始字符串的片段注意脱敏。这样连续一段时间后你就能看出哪些接口是高频出错源然后集中优化。以前我总觉得这类报错纯粹是运气不好碰到脏数据但后来发现几乎所有接口的 JSON 错误都有规律可循。比如某个报表接口总在凌晨因数据量剧增被网关截断某个消息接口总因为用户输入文本里的半角引号而出错。把这些规律暴露出来以后要根治就顺理成章了。6.4 给前端的最后一道防线关键页面要有失败兜底 UI就算上面这些都做了还是有概率在极端情况出现解析失败。这时前端至少要做到三点不白屏、不死循环。给出一个明确的数据加载失败提示。把问题上报而不是只在自己电脑上复现一遍就完了。总结成一句话就是SyntaxError: Expected , or } after property value in JSON at position 134这个报错本质上是数据在你不知道的哪个环节变得不合法了它可能是后端的锅、网关的锅、代理插件的锅也可能就是你自己的拼接代码捣了个乱。排查它的核心不是记下报错信息的固定解法而是掌握一套从最终字符串出发按链路逐层回溯的方法。拿到原始字符串复现报错标记位置看这一段是什么形状然后判断数据结构问题出在哪个层再去改该改的地方。这套流程走熟了基本上十分钟内能从一团乱麻里拎出头绪。我个人还有一个压箱底的小技巧排查这类问题时不要相信格式化后的 JSON 长什么样永远以JSON.stringify打印出来的转义后的字符串为准。因为在转义输出里不可见字符、引号、换行符都会原形毕露一眼就能看出问题所在。多养成这个习惯你会发现自己排 JSON 错的速度又快了一截。