工具
指南
本页内容

2026年8月9日

JSON 格式化与校验实践:把报错定位到行列,而不是靠猜

每个开发者都有过这样的经历:把一段 JSON 粘进解析器,得到一句 Unexpected token 什么什么,然后对着一整屏的文字发呆,试图肉眼找出问题。报错信息很少用大白话告诉你”错在哪”——但几乎总会告诉你”在哪个位置”,这就够了。这篇指南要讲的就是如何把这个”位置”变成两分钟的修复:格式化为什么重要、哪些错误你真的会遇到、以及如何按行列去读一个报错位置,而不是靠猜。

文中所有操作都可以直接在本站完成。JSON 格式化校验器在浏览器里完成解析,报错带一一起始的行号和列号;文档通过校验后,再用 JSONPath 查询 工具把需要的值抽出来。你粘贴的任何内容都不会离开本机——这一点在调试生产环境的请求体时尤其重要。

格式化 JSON 到底值不值#

机器不在乎 JSON 长什么样。{"a":1} 和同一个对象展开成 40 行的漂亮打印,解析之后逐字节等价——语义相同、合法性相同。格式化是给人看的,而它的回报体现在三件具体的事上。

看差异(diff)。 版本控制按行比较文件。一个压缩成单行的 5000 字符 JSON 文件,任何改动都会产生一行无法阅读的巨大 diff;同样的文档换成两空格缩进,改动就是一小块可以直接评审的内容。配置文件或 API 固定数据如果要进仓库,统一格式不是装饰——这是代码评审能进行的前提。

看清结构。 缩进让嵌套关系可见。当一个对象被放进了错误的数组,或者一个右花括号提前结束了错误的作用域,缩进会立刻显形。而在一整行扁平文本里,同样的错误要等到下游以”缺键”报错时才会暴露。

错误定位。 这是本指南的核心收益。“第 12 行第 9 列有错”这种信息,只有当你的输入真的有行有列时才可用。如果 JSON 是一整行,所有错误都报在第 1 行,列号动辄上千。先格式化、再排查——错误位置立刻变得有意义。

当然也有反面场景:传输体积。用本文实例数据里的一张订单对象算过——含一个客户和两个明细项,两空格缩进格式化后 286 字节,压缩后 179 字节,缩小了 37%。所以通行约定是”仓库和日志里用漂亮格式、线上传输用压缩格式”。两种形态可以无损互转,因为它们都是对同一个解析后的值重新序列化的结果。

格式化器实际做了什么#

理解这一点能省掉日后一整类困惑。真正的格式化器并不”编辑”你的文本,它走三步:

  1. 用真正的 JSON 解析器解析输入。解析抛异常就到此为止——返回的是校验错误。
  2. 可选地对解析结果做变换——比如按字母序重排对象键。
  3. 按你选的缩进把值重新序列化成文本。

由此有两个推论。第一,格式化是”规范化”:它产出的是你数据的规范序列化,不是在你原文里插空格。你以为没问题的注释、从没注意到的重复键、1.50 与 1.5 的写法差异——全部在解析这一步尘埃落定,输出展示的是解析器实际看到的东西。第二,格式化就是”附带输出的校验”:能干净格式化的文档必然是能干净解析的文档,反之亦然。不存在”合法但格式化不了”的状态。

缩进选择是风格问题,但有一条实用边界:两空格和四空格都是空格(在任何终端、任何复制粘贴中不变形),制表符缩进的漂亮输出最紧凑、粘贴进用 tab 的编辑器也正确。每个项目定一个,然后别再讨论。键排序值得开的场景是:比较两份内容可能相同、键序不同的文档——排序序列化让比较回归到纯文本层面。

你真正会遇到的五类错误#

JSON 的语法很小,也毫不留情。实际工作中,你见到的非法 JSON 几乎逃不出这五类。下面每个例子都是真实数据:报错信息来自现代引擎,位置按本站格式化器完全相同的方式计算得出。

一、单引号写成了双引号的活儿。 JSON 的字符串和键必须用双引号;单引号是 JavaScript 的习惯,语法直接拒绝。

{
  'host': 'api.example.com',
  "port": 443
}

引擎报错:Expected property name or '}'——定位在第 2 行第 3 列,正是第一个单引号。列号指向解析器卡住的那个字符,所以修复通常就是盯着那个点看。

二、尾逗号。 在 JavaScript 的数组与对象里很方便;在 JSON 里非法。

{
  "users": [
    {"id": 1, "name": "Ada"},
    {"id": 2, "name": "Linus"},
  ],
  "total": 2
}

报错 Unexpected token ']'——解析器在上一行的逗号之后期待另一个值,却见到了右方括号。报错位置在 ],但病灶是它前面第 4 行的那个逗号。学会”往前看一个记号”,是读懂这类报错的一半功力。

三、成员之间漏了逗号。 上一类的镜像:

{
  "retries": 3,
  "timeout": 30
  "region": "eu-west"
}

解析器读到 "timeout": 30,期待 , 或 },却遇到了一个字符串:Expected ',' or '}' after property value。位置指向 "region" 的开头——也就是下一个成员开始的行,恰好比你漏写逗号的那行低一行。

四、字符串里出现未转义字符。 字符串值里有真实的换行,或内嵌引号:

{
  "note": "line1
line2"
}

引擎报 Bad control character in string literal,位置在 line1 之后。字符串必须把控制字符转义为 \n、内部双引号转义为 \"。有人从日志或聊天窗口里复制 JSON 片段、复制过程断行了,也会出现这个错。

五、数字或字面量写法不合法。 JSON 不允许注释、不允许 NaN、不允许 Infinity、不允许十六进制、不允许前导零。{"ts": 0x1F}、{"n": NaN}、// comment 都是硬性解析错误。报错措辞各异(Unexpected token 'x'、Unexpected token 'N'),但类别相同:语法只认它认的东西。

一个整体观察:这五类全是词法错误——解析器压根没走到结构或内容层面。JSON 在解析期也只有这一种错误,所以错误清单才这么短、这么好记。

行列位置是怎么算出来的#

解析器抛错时会附带一个字符偏移量——从输入开头到失败点的字符数。光秃秃的偏移量对人类极不友好(“position 4”),所以格式化器要做一次换算:从输入开头逐字符走一遍,每读一个字符列号加一,遇到换行符就把列号归零、行号加一。结果就是编辑器行号槽里能直接找到的一一起始行列。

知道这个换算过程,就能解释你可能留意过的两个怪现象。第一,输入只有一行时,任何偏移都映射到第 1 行——把输入格式化(或至少给它断行),位置立刻可用。第二,如果引擎压根没法从报错信息里提取偏移量,一个靠谱的校验器会退化为扫描或报告”输入末尾”,而不是编造一个位置。那种不管实际错误在哪、一律报”第 1 行第 1 列”的校验器不是在定位你的错误,是在猜。

排查顽固文档的实操流程:粘进格式化器,读出行列,在编辑器里跳过去,修掉,重复。大文档往往不止一个错——因为作者会把同一个错误(比如尾逗号)在好几处都犯一遍——每修一个就暴露下一个位置,来回三四轮很正常。

实例演练:先格式化,再查询#

文档一旦能解析,格式化只是开始——多数时候你要的是从中抽取点什么。这正是查询语言的位置。看这张订单对象,两空格缩进格式化(286 字节,压缩 179 字节):

{
  "order": "A-3788",
  "customer": {
    "id": 9042,
    "email": "[email protected]"
  },
  "items": [
    { "sku": "KB-01", "qty": 1, "price": 129.00 },
    { "sku": "CB-02", "qty": 2, "price": 18.50 }
  ],
  "total": 166.00,
  "currency": "USD"
}

对这份文档的 JSONPath 表达式与经过验证的结果:

  • $.total 返回 166,JSON Pointer 为 /total
  • $.customer.email 返回 [email protected]
  • $.items[*].sku 返回两个 SKU——KB-01 和 CB-02,分别在 /items/0/sku 与 /items/1/sku
  • $..price 递归查找任意深度的价格:129 和 18.5
  • $.items[?(@.qty > 1)].sku 按条件过滤,返回 CB-02——数量大于一的那个明细项

注意最后两个例子。递归下降 $.. 回答的是”这个键在文档的任何地方出现在哪”——在不是你写的配置文件里查键,这是神器。过滤表达式 [?(@.qty > 1)] 回答”哪些条目满足条件”——这种查询平时要写五行脚本。一个能在格式化输出里高亮匹配跨度的查询工具,等于把 JSON 调试变成了可视任务:路径告诉你匹配了什么,高亮告诉你它在哪。

在本站试一试#

两个工具覆盖上述全部流程,且都在浏览器里运行——无上传、无后端,粘真实报文也安全。

先上 JSON 格式化校验器。把上面五类坏例子粘进去(单引号和尾逗号那两个要手敲一遍),看报错带着行列号回来;然后粘订单文档,在两空格、四空格、制表符之间切换,开关键排序看规范化的排序形态(顶层键变为 currency、customer、items、order、total),再对比压缩与漂亮格式的字节数。

然后带着同一张订单文档去 JSONPath 查询工具。把上面五条表达式各跑一遍核对结果;匹配值会在格式化文档的对应位置高亮。当一份真实的 API 响应摆到你桌上,这套”格式化校验 + 查询抽取”的组合能替代大半为看一眼内容而写的临时脚本。

常见问题#

JSON 缩进用 tab 还是空格?#

都合法,一致性才重要。两空格是现代工具链里最常见的约定,在嵌套很深的文档里不会太宽;tab 的漂亮输出最紧凑。既然任何格式化器都能无损互转,加一道自动化检查(格式化该文件,若有变化则构建失败)就能永久终结这场争论。

尾逗号为什么算错?看起来无害。#

因为语法定义里逗号是成员之间的分隔符,} 前的逗号等于承诺了后面还有一个成员、但它没来。有些解析器提供宽松模式,在解析前剥掉尾逗号——读第三方改不了的文件时有用;但在自己的流水线里默认开宽松,会把真正的错误也藏起来,比如某成员被删掉、逗号忘了删。

JSON 里键的顺序重要吗?#

对合规的解析器不重要——对象按定义是无序的。对人和 diff 重要:稳定、有序的键让两份内容相同的序列化在文本上也相同,比较和评审因此变得简单。键排序选项做的正是这件事:规范化,不改语义。

我的 JSON 合法但特别大,有什么建议?#

排查用漂亮格式、传输用压缩格式、看内容用查询而不是滚动。一份大到读不动的文档,恰恰是”查询表达式($..price)+ 跨度高亮”胜过肉眼逐行的时候。另外注意,超大的单行输入会让错误列号大得离谱——先给它断行再排查。

JSON 里能写注释吗?#

不能——语法里没有注释这个产生式。所以需要注释的配置格式要么扩展 JSON(在自己的解析器里加注释支持),要么干脆换格式。解析前用正则剥掉注释行是常见的临时手段,宽松格式化器做这事更稳一些,但产物已不再是 JSON。

小结#

格式化是”带人类可读输出的校验”:解析这步是正确性的证明,序列化是给你的眼睛、你的 diff、你的评审者看的。报错信息乍看晦涩,但只要认全真正会遇到的五类——引号、尾逗号、漏逗号、未转义字符、不合法字面量——并且记住”报错位置可能指向病灶后一个记号”,它就变得可读。行列定位把引擎的偏移量换算成编辑器能跳转的坐标;查询则把一份通过校验的文档变成答案。

在本站把整个闭环练一遍:先用 JSON 格式化器校验排版,再用 JSONPath 查询工具抽取高亮。

← 全部指南