JSON 格式化器(formatter / beautifier / pretty printer / validator)不是装饰器,是 API 调试、配置文件管理、数据交换、JWT payload 解码的可读性基础设施。大多数开发者的格式化习惯是「在 IDE 里按一下格式化快捷键」,这能解决 30% 的问题,剩下的 70%(trailing comma、Unicode 转义、大整数精度丢失、BOM 字符、单引号、NaN / Infinity 序列化、排序键副作用)是 IDE 解决不了的。
JSON 格式化器的核心价值不是「让 JSON 看起来好看」,是让数据在 API 调试 / 配置 review / 日志排查 / JWT 解码时被人眼读得懂。所以它属于基础设施,不是美化器。本文 8 个真实踩坑,每个「症状 + 为什么 + 怎么修」三段式讲清楚,文末给 Piick JSON 格式化工具,语法高亮 + 排序键 + 容错解析(JSON5 / 单引号 / 注释)+ 错误位置点击跳转 + 5 项统计(键 / 数组 / 最大嵌套深度 / 字节),1 分钟走完。
30 秒总览
- JSON 格式化 ≠ 美化:前者是 API / 配置 / 日志的可读性基础设施,后者只是「让 JSON 看起来整齐」
- JSON 标准的 4 个硬约束:不允许 trailing comma、字符串必须双引号、不允许注释、不允许 NaN / Infinity / undefined(4 个工具开发者必踩)
- JS 生态的 3 个常见误用:trailing comma(ES2017+ 合法)、单引号对象(JS 字面量)、模板字符串嵌套 JSON(转义灾难)
- 8 个常见 foot-guns:trailing comma / Unicode 转义 / 大整数精度 / 缩进 ≠ 嵌套 / BOM 字符 / 单引号 / NaN-Infinity-undefined 序列化 / 排序键副作用
- 修复方向:永远用工具校验(尤其是生产 API 配置)、永远容错解析(开发期)、永远保留大整数为字符串(Snowflake ID)、永远不自动排序键(影响 Git diff)
- 用 Piick JSON 格式化工具在线格式化 / 校验 / 美化,容错解析 + 语法高亮 + 错误位置点击跳转 + 5 项统计,数据本地化不上传
8 个常见 Foot-Guns
Foot-Gun 1:trailing comma 被标准 JSON 拒绝
症状:从 Chrome DevTools 「Copy as fetch」复制的 response body,粘贴到 JSON formatter 报「Unexpected token」。你以为是工具的 bug — 实际上是因为 DevTools 复制的 JSON 带了 trailing comma(最后一个 key 后多一个逗号),而标准 JSON 不允许 trailing comma。
为什么:ECMAScript 2017+ 允许对象字面量的 trailing comma,所以 Chrome / Firefox 的 DevTools 复制出来的 JSON 经常带逗号。Python 3.7+ 字典也允许。但 JSON 规范 RFC 8259 第 5 章明确写「trailing commas are not allowed」。naive 的 JSON.parse 严格遵循规范,看到 {"a": 1,} 就报错。
修复:生产环境的 JSON 必须无 trailing comma。开发环境可以用工具的「容错解析」选项 (Piick JSON 格式化工具支持 JSON5 风格,接受 trailing comma + 单引号 + 注释),但提交到 API / 配置文件前必须 lint 掉。CI 跑一遍 prettier --check 或 eslint --rule no-trailing-commas 兜底。
真实场景:前端 fetch 后端 API,Chrome DevTools 「Copy as fetch」,粘贴到 jq 跑 jq .response.users[0].id,报错。原因是 DevTools 输出的 JSON 带 trailing comma,qj 是 strict 解析。永远用 jq 之前先 lint 一次,或者直接用 jq 容错模式 jq -R .。
Foot-Gun 2:Unicode 字符没转义
症状:后端返回 {"name":"张三"},前端 JSON.parse 没问题。但有时候同样的数据粘贴到工具报「Unexpected token」,或者某些库 stringify 出来的 JSON 字符串里中文变 ?。这是 Unicode 转义不彻底或不一致。
为什么:JSON 标准规定字符串中的非 ASCII 字符可以直接以 UTF-8 出现(因为 JSON 文档本身要求 UTF-8),也可以转义成 \uXXXX。两种格式都合法。但有些库的默认行为不一致:
- Python
json.dumps默认ensure_ascii=True,把所有非 ASCII 转成\uXXXX - JavaScript
JSON.stringify默认保留原始 Unicode - Java
ObjectMapper默认转义 - Go
encoding/json默认转义
后端 Python 输出 {"name":"\u5f20\u4e09"},前端 JS 解析得 张三,但你复制到不支持 \uXXXX 解析的工具里就看到一堆转义码。
修复:永远确保生产环境 JSON 是 UTF-8。Python 后端用 json.dumps(..., ensure_ascii=False) 输出可读格式(配合 Content-Type: application/json; charset=utf-8)。调试时用工具的容错解析,把 \uXXXX 当作合法输入。
真实场景:Python 后端 dump 出来 {"msg":"\u4f60\u597d"},前端 fetch 后 response.json() 拿到 {msg:"你好"},正确。但你把后端原始响应粘贴到 vim 里复制给同事,同事看到 {"msg":"\u4f60\u597d"} 一脸懵。这就是 UTF-8 vs \u 转义的不一致。
Foot-Gun 3:大整数精度丢失(Snowflake ID 末位变 0)
症状:后端返回 {"user_id": 1234567890123456789},前端 JSON.parse 后 user_id 是 1234567890123456800,末位 3 位都是 0。后端给的 ID 是对的,前端取出来的 ID 是错的,数据库查询 404。
为什么:JavaScript Number 是 IEEE 754 双精度浮点数,安全整数范围是 2^53 − 1 = 9007199254740991(约 9 × 10^15)。超过这个范围的整数会丢精度。Snowflake ID 是 19 位整数(约 10^18),JSON.parse 直接丢精度。Python int / Java long 没有这个问题,但 JS 是最常用的前端语言。
修复:
- 后端:
bigint序列化成字符串("1234567890123456789"),而不是数字 - 前端:解析时用
json-bigint库,或者全部按字符串处理 - 展示:UI 显示时再
BigInt(id).toString(),计算时用bigint类型 - API 设计:返回 ID 时考虑直接用 UUID(36 字符)或 ULID(26 字符),都是字符串格式
用 Piick JSON 格式化工具校验 API 响应时,先看 tools.jsonFormatter.input 的 placeholder,粘贴后看输出是否一致;如果 ID 长度 > 15 位,警惕大整数精度问题。
真实场景:Twitter Snowflake ID 是 64-bit 整数,约 1770000000000000000,远超 2^53。前端 fetch tweet id 1234567890123456789,实际拿到 1234567890123458000,查不到。Snowflake ID 必须用字符串序列化。
Foot-Gun 4:缩进 ≠ 嵌套关系
症状:你的代码用 2 空格缩进,同事的代码用 4 空格缩进,PR review 看着完全不一样的 JSON,但解析结果完全一样。你以为是嵌套深度不同,实际是 {"a":{"b":{"c":1}}} 和 {"a": { "b": { "c": 1 } }} 是同一个对象。
为什么:JSON 的语法不在乎空格,只要符合 RFC 8259 词法规则,任何数量的空格 / tab / 换行都是等价的。缩进只是「人眼可读性」,跟实际嵌套无关。这也是为什么 JSON 文件用 git diff 时,即使加了 / 删了一个字段,看起来可能整片都都变了(因为缩进重排)。
修复:统一缩进风格(推荐 2 空格,因为更省行宽)+ 用 prettier / jq 自动格式化,避免人手格式化。用工具的「缩进」选项 (Piick JSON 格式化工具支持 2 空格 / 4 空格 / tab / minify),团队统一一种。
真实场景:JSON 配置 100 个字段,2 空格和 4 空格对比:
- 2 空格:
{"a":1,"b":2,"c":{"d":3}}紧凑 - 4 空格:
{"a": 1, "b": 2, "c": { "d": 3 }}宽松
嵌套深度 5+ 时,4 空格每层 4 字符,5 层嵌套 = 20 字符左边距,屏幕宽 80 字符只剩 60 字符内容。2 空格更适合深嵌套。
Foot-Gun 5:BOM 字符被忽略
症状:JSON 文件明明在 VSCode 里看着正常,粘贴到工具 / Node.js JSON.parse 报错「Unexpected token」。右键 → 查看编码 → UTF-8 with BOM。原因是文件开头有一个隐形字符 \ufeff(BOM, byte order mark)。
为什么:Windows 记事本 / 部分老编辑器保存 UTF-8 文件时,默认加 BOM(3 字节 EF BB BF)。JS / Python / 大多数 JSON 解析器看到 \ufeff 当 JSON token 就报错。JSON 规范不允许 BOM 在 JSON 文本里(除了在 JSON 文档字符串内部,作为转义字符)。
修复:
- 保存时无 BOM:VSCode 右下角 → UTF-8 with BOM → UTF-8(默认)
- 命令行转码:
sed '1s/^\xef\xbb\xbf//' input.json > output.json - Python 读取:用
encoding='utf-8-sig'自动剥离 - Node.js:用
fs.readFileSync(path, 'utf-8').replace(/^\ufeff/, '')
用 Piick JSON 格式化工具时,粘贴含 BOM 的 JSON 会报「Unexpected token」,手动删 BOM 或用 sed 预处理。
真实场景:Windows 同事写 JSON 配置 → 保存 → 提交 → CI 跑 JSON.parse → 报错。CI 不会自动 strip BOM,需要在源头改编辑器设置。.editorconfig 里加 insert_final_newline = true 和「编码 UTF-8 无 BOM」,团队统一。
Foot-Gun 6:单引号当 JSON
症状:Python 字典 {'a': 1, 'b': 2} 直接复制到 JSON formatter 报错。Python 字典默认输出格式跟 JSON 很像,但用单引号,不是合法 JSON。
为什么:JSON 标准明确规定字符串必须用双引号,单引号不行。Python 字典 repr() 输出单引号,JSON 字符串输出双引号:
新人很容易把 print(d) 当 JSON 用,粘贴到工具就报错。
修复:
- Python 调试时用
print(json.dumps(d, indent=2))而不是print(d) - 工具的「容错解析」选项接受单引号(Piick JSON 格式化工具支持 JSON5 风格)
- 教育团队「Python dict ≠ JSON,Python str 用单引号,JSON 用双引号」
真实场景:Python 新人 debug API 响应,print(response) 输出 {'code': 200, 'msg': 'ok'},以为是 JSON 复制给后端同事,后端同事 JSON.parse 报错。新人 debug 第一坑。
Foot-Gun 7:NaN / Infinity / undefined 序列化
症状:JS 对象 {a: NaN, b: Infinity, c: undefined} 用 JSON.stringify 序列化后是 {"a":null,"b":null}(NaN 和 Infinity 变 null),undefined 字段直接消失。如果后端用 strict JSON parser(比如 Python json.dumps 再 json.loads),行为不一致:Python json.dumps 序列化 float('nan') 是 NaN(非标准),json.loads('NaN') 报错。
为什么:JSON 标准不允许 NaN / Infinity / undefined,但 JS 是日常用这些的语言:
NaN/Infinity:JS 全局常量,数值计算错误时产生undefined:JS 「变量未定义」的标志JSON.stringify:把它们都当null处理(undefined 直接丢字段)JSON.parse('NaN'):JS 是宽容的,接受为NaN(非标准)json.dumps(float('nan')):Python 输出NaN(非标准)json.loads('NaN'):Python 默认 strict,报错
跨语言数据交换时,NaN / Infinity / undefined 的序列化行为不一致,容易出 bug。
修复:
- API 规范:永远不使用 NaN / Infinity,无效值用
null或省略 - 数据清洗:序列化前手动
if (x === undefined || Number.isNaN(x)) x = null“ - 后端严格校验:Python
json.dumps(..., allow_nan=False),报错时直接 fail-fast - 库:JSON Schema validation 在 API 入口校验,拒绝 NaN / Infinity
用 Piick JSON 格式化工具时,粘贴含 NaN / Infinity 的 JSON 会报「Unexpected token n」(因为 NaN 不是合法 token),手动替换为 null。
真实场景:前端 fetch 后端 API,响应 {score: NaN}(后端 bug),前端 JSON.parse 后 score: NaN,SQL 查询 WHERE score > 0 不返回任何行(NaN > 0 是 false),UI 显示「无数据」,调试半天才发现是 NaN。永远不要传 NaN,后端做防御性校验。
Foot-Gun 8:排序键改变了 Git diff / 语言语义
症状:你把工具的「sort keys」打开,JSON 看着整齐了,git commit 后同事 review 看到 100 行 diff,全是字段顺序变化,没有实际内容变化。或者更严重的:Go map / Python 3.7+ dict 在某些边缘情况下对 key 顺序敏感,排序后语义变了。
为什么:JSON 对象是 unordered map 的标准定义,但 RFC 8259 实际允许实现保留顺序(因为 JS 对象字面量、Python 3.7+ dict 都是有序的)。工具的 sort keys 选项是为了显示好看,但:
-
- Git diff 噪音,影响 review 质量
-
- 对 key 顺序敏感的序列化协议(JSON Canonicalization Scheme)出错
-
- 某些库的
Object.keys(obj)顺序依赖插入顺序,排序后forEach顺序变了
- 某些库的
修复:生产环境的 JSON 不要自动排序键。开发期用工具看结构就好,提交前用 jq 或 prettier 重新重新format(保留原始顺序)。如果团队真的要统一顺序,用 lint 强制(sort-package-json for package.json),不要依赖工具运行时排序。
用 Piick JSON 格式化工具时,默认关闭 sort keys,只在「我要看结构」时手动开启。
真实场景:同事打开 sort keys,提交 1000 行 package.json 全部重排,reviewer 看 diff 看到 800 行是顺序变化,200 行是新增依赖,2 小时 review 全部浪费。不要在生产配置上用 sort keys。
工具选型决策
选项 A:IDE 内置格式化(VSCode / WebStorm)
适合:每天写 JSON、已经用 IDE 的人。
优点:实时格式化、不离开编辑器、跟 schema 集成(自动补全 key)。缺点:只能本地 JSON、不能跨工具同步(IDE 配的格式跟 jq 配的不一样)、不支持批量处理(团队 CI 自动格式化得另搭一套)。
选项 B:命令行工具(jq / python -m json.tool)
适合:CI / build pipeline 里自动格式化、有 shell 环境的项目。
优点:脚本化、可批量、可结合 lint、CI 集成。缺点:不直观(文本输出没颜色)、不支持 syntax highlight、不支持容错解析(JSON5 / 单引号 / 注释)。
选项 C:Piick JSON 格式化工具
适合:跨工具调试(API 响应 + 配置文件 + JWT payload)、不想装额外 npm 包(纯前端项目)、敏感数据(生产 API 配置 / 含 PII 的 JSON)不想过任何第三方服务器。
优点:零依赖(纯前端 JSON.parse + tokenizer)、语法高亮 + 排序键 + 容错解析(JSON5 / 单引号 / 注释)+ 错误位置点击跳转 + 5 项统计(键 / 数组 / 最大嵌套深度 / 字节)、数据本地化。缺点:不能集成到 CI(用选项 B 补)、不能自动 save(纯前端工具)。
决策树:
- 已经在写 JSON → 选项 A(IDE 插件)
- CI 集成 + shell 环境 → 选项 B(jq)
- 跨工具调试 + 敏感数据 + 不装东西 → 选项 C(Piick)
5 个真实场景
场景 1:API 响应调试
前端调后端 API,response 是压缩的 minified JSON,粘贴到工具自动美化 + 折叠 / 展开 + 显示统计(键 / 数组 / 深度)。
步骤:
- fetch API → response.text() → 粘贴到 Piick JSON 格式化工具
- 自动 syntax highlight(5 种 token 类型)
- 看顶部 stats:
{keys: 47, arrays: 12, maxDepth: 4, bytes: 1024} - 错误位置点击跳转:JSON.parse 报错时点错误 banner,跳到失败的字符位置
- 复制格式化结果 / 折叠嵌套层级
真实建议:永远用工具看 API 响应,不要直接在 console.log 看 minified JSON,人眼读不动。
场景 2:package.json / 配置文件校验
写 package.json 时 typo(多打逗号 / 缺右括号 / 字符串没闭合),CI npm install 报错,但编辑器不报错(IDE 容错)。用工具跑一遍校验。
步骤:
- 复制 package.json 内容 → 粘贴到工具
- 工具报错 → 看「Line X, column Y」错误位置 → 点击跳转 → 修正
- 特别检查:
:"scripts:","dependencies","devDependencies"三处(最容易打错) - 提交前跑
npm install --dry-run二次确认
真实建议:用 Piick JSON 格式化工具的「容错解析」选项(tolerant)在开发期期看「语法错误但接近正确」的提示,比 strict parser 友好。
场景 3:JWT payload 解码
JWT 是 base64url 编码的 JSON,header.payload.signature 三段。解码 payload 看 claims 时,先用 Piick JWT Decoder(同 doc cluster)拆 base64,再用 JSON 格式化器校验解码后的 JSON 是否合法。
步骤:
- JWT
eyJhbG...sw5c - Piick JWT Decoder拆三段
- payload 是
{"sub":"1234567890","name":"John Doe","iat":1516239022}← minified JSON - 粘贴到 Piick JSON 格式化工具美化 + 看时间戳字段(
iat/exp/nbf转可读时间) - 检查
exp是否过期
真实建议:JWT 调试链:JWT Decoder → JSON Formatter → Unix Timestamp Converter(同 doc cluster),3 件套覆盖完整流程。
场景 4:Snowflake ID / 大整数 ID 序列化
后端用 int64 返回 ID,前端 fetch 后 id.toString() 末位变 0。字符串化处理。
步骤:
- 后端 Python
json.dumps({"id": int(2**60)})→ 输出{"id": 1152921504606846976}← 末位可能丢精度 - 修复:后端
json.dumps({"id": str(2**60)})→ 输出{"id": "1152921504606846976"}← 字符串,前端按字符串处理 - 前端
BigInt(id).toString()做数学运算 - UI 显示
id.slice(0, 4) + "..." + id.slice(-4)(类似银行卡号显示)
真实建议:API 规范:大整数 ID 永远用字符串。前端永远不要 assume ID 是 Number。
场景 5:i18n 资源文件 / 多语言字典
i18n JSON 文件含中文 / 日文 / emoji,正确处理 Unicode 转义。开发期用工具可视化,生产期用 UTF-8 + ensure_ascii=False 输出可读格式。
步骤:
- i18n 文件
zh.json:{"hello":"你好,世界!🌏"}← 直接 UTF-8 - 粘贴到 Piick JSON 格式化工具验证 UTF-8 字符正常显示
- 后端输出用
json.dumps(..., ensure_ascii=False)(Python)或JSON.stringify(JS 默认) - 响应头:
Content-Type: application/json; charset=utf-8 - CI 加 lint:
eslint-plugin-unicorn或自定义脚本检查ensure_ascii配置
真实建议:永远不要在 JSON 字符串里写 \uXXXX 转义,开发期用 UTF-8 直接写可读字符。生产期让框架 / 库决定怎么序列化,自己配置 ensure_ascii=False。
推荐做法
- 永远用工具校验 JSON(尤其是生产 API 配置),不依赖 IDE 容错
- 永远容错解析开发期(工具的 tolerant 模式),production lint 无 trailing comma + 无注释 + 无单引号
- 大整数 ID 永远用字符串序列化(Snowflake ID / 分布式主键),不假设前端 Number 安全
- 不要自动排序键(影响 Git diff 和 key 顺序敏感语言),只在「看结构」时手动开启
- 统一缩进(推荐 2 空格)+ 用 prettier / jq 自动格式化,避免人手格式化
- 永远用 UTF-8 + Content-Type charset=utf-8,不要用
\uXXXX转义(开发期可读性差) - CI 跑 JSON Schema validation,在 API 入口拒绝 NaN / Infinity / 严格模式字段缺失
- Windows 编辑器关闭 BOM,
.editorconfig团队统一,避免 BOM 字符在 CI 报错 - 用 Piick JSON 格式化工具在线格式化 / 校验 / 美化,语法高亮 + 容错解析 + 错误位置点击跳转 + 5 项统计 + 数据本地化不上传。搭配 Piick JWT 解码器拆 base64、Piick SQL 格式化工具格式化 SQL、Piick URL 编码解码器处理 URL 参数、Piick Cron 生成器生成 cron 表达式、Piick 正则测试器测试 JSON 字段值正则 — 5 件套覆盖 JSON 上下游所有 dev 场景
JSON 格式化不是美化,是 API / 配置 / 日志的可读性基础设施。真正的「格式化对」 = 标准 JSON + UTF-8 + 大整数字符串 + 不排序 + 不带注释 + 不带 trailing comma + 团队统一缩进。把 Piick JSON 格式化工具 收藏在书签栏,调试 API / 校验配置 / 解析 JWT payload 时开一下 1 分钟走完,日常 dev 提效 30%。