从缩进陷阱到嵌套转义:JSON 格式化/校验工具的进阶用法记录

0 阅读9分钟

JSON 大概是现在最常见的数据交换格式了。接口返回是它,前端本地存储是它,CI 配置、日志埋点、消息队列里也全是它。但我在实际工作中还是被它折磨过不少次:日志里复制出来的一长串字符串全挤在一行,末尾带着莫名其怪的逗号;某个字段值里又嵌了一层 JSON,引号前面全是反斜杠;两个环境的响应明明内容一样,字段顺序不同导致 diff 满屏飘红。

前几天联调一个 webhook 回调,对方把整条消息体贴到群里。我复制出来直接 JSON.parse,控制台只抛出一句 Unexpected token , in JSON at position 384,连行号都没有。我顺手打开了一个在线 JSON / XML / YAML / CSV 转换器,本想只做下格式化,结果发现几个被忽略的功能省了我不少事,记录一下。

一、格式化之前先定位语法错误

那段日志贴进输入框后,工具先给我报了错,并且标了大致行号。原因是日志截断时,某个对象最后一个字段后面多了一个逗号。这种错误我从日志、Swagger 示例、甚至同事的消息里复制时经常遇到。

screenshot_01.png

把逗号删掉再格式化,输出立刻变得可读。我习惯根据最终用途选缩进:

  • 2 空格:贴到文档、聊天、Issue 里,横向空间占用小;
  • 4 空格:写到代码仓库的配置文件或测试用例里,和项目 Prettier 配置保持一致;
  • 压缩:塞进 URL 参数、埋点字段或 Redis 值里,减少体积。

从浏览器 DevTools 复制响应时,尽量切到 Preview 标签再复制,比直接从 Response 原文字符串里复制少很多转义问题。大段日志我倾向于先保存文件,再上传到工具的“上传文件”入口,避免粘贴时带进 BOM 头或特殊换行符。

二、校验行号只是开始,常见错误有规律

以前 JSON 报错,我的排查方式是先把字符串一段段注释掉再跑 JSON.parse,效率很低。这个转换器在解析失败时会给出错误信息和行号,虽然不一定精确到字符,但至少能让我把目光先锁定在那一块。

screenshot_02.png

最常见的报错原因我列了一下:

  • 键名没加双引号,尤其是从 JavaScript 对象直接复制过来的;
  • 字符串用了单引号,JSON 标准只认双引号;
  • 末尾多了逗号;
  • 缺少闭合括号,大段 JSON 中间被截断;
  • 非法转义,比如 \u 后面跟了非十六进制字符;
  • 注释残留,JSON 标准不支持注释。

有一次我遇到 Unexpected end of JSON input,找了半天发现是消息体被日志组件截断了,后半段直接丢失。工具提示行号在最后一行,反而帮我确认了问题不在 JSON 本身,而在数据源。

三、键排序让 diff 更干净

需要对比两个环境或两次请求的返回是否一致时,字段顺序不同会让普通文本对比工具满屏标红。我会先把两段 JSON 都丢进转换器,点“键升序”,再对排序后的结果 diff,真正的差异就只剩数值本身了。

// 排序前
{
  "userId": 1001,
  "name": "Tom",
  "age": 28,
  "roles": ["admin", "editor"]
}

// 键升序后
{
  "age": 28,
  "name": "Tom",
  "roles": ["admin", "editor"],
  "userId": 1001
}

键排序只会重排对象的键,不会改动数组元素的顺序,所以数组里的业务顺序不会被打乱。这个功能在做接口回归测试时特别顺手,把测试响应和线上响应都排序后保存成快照,diff 时基本不会因为字段顺序产生伪差异。

四、嵌套 JSON 字符串的转义与去转义

最让我头疼的是“JSON 套 JSON”。消息队列、webhook、旧系统接口里经常出现这种结构:外层是一个 JSON 字符串,字符串内容本身又是一段 JSON。

"{\"event\":\"order_paid\",\"payload\":{\"orderId\":\"A123\",\"amount\":19900}}"

手动删反斜杠既慢又容易出错。我直接把整段带转义的文本贴进输入框,点“去转义”,立刻得到可读版本:

{
  "event": "order_paid",
  "payload": {
    "orderId": "A123",
    "amount": 19900
  }
}

screenshot_04.png

反过来,如果要把一段 JSON 嵌进代码字符串、数据库字段或脚本参数里,点“转义”就能生成带反斜杠的版本。需要注意工具的“转义”是按字符串处理的,不会在两端再加引号;如果你要的是 JSON.stringify(obj) 的完整结果,还需要自己补外层引号。

我踩过一个小坑:把已经合法的 JSON 直接点“去转义”,结果把正常的 " 也拆开了,整段数据反而变成非法格式。所以我现在会先看输入里反斜杠的密度,密度高才用去转义。

五、树形视图:深层级 JSON 的导航器

有些接口返回的 JSON 层级很深,光是滚动找字段就很累。输出区右侧有个“树形视图”开关,点开后会把 JSON 渲染成可折叠的树。根节点显示总项数,叶子节点按类型上色:字符串绿色、数字蓝色、布尔值紫色、null 灰色、对象和数组橙色。

screenshot_05.png

我最近用它来看一个第三方开放平台的权限返回,嵌套了五六层,树形视图让我不用来回滚动就能定位到 data.items[3].permissions 那一块。这个视图对理解陌生的 OpenAPI 响应也很有效,折叠后一眼就能看到哪些是对象、哪些是数组,比直接看原始 JSON 快得多。

六、JSON 与 YAML、CSV、XML 互转的坑

这个工具支持 JSON、XML、YAML、CSV 四者互转,我用得最多的是 JSON 转 YAML。

比如把一段接口配置:

{
  "server": {
    "host": "0.0.0.0",
    "port": 8080,
    "timeout": 30
  },
  "features": ["logger", "cache", "auth"]
}

转成 YAML 后贴到 Docker Compose 或 CI 配置里:

server:
  host: 0.0.0.0
  port: 8080
  timeout: 30
features:
  - logger
  - cache
  - auth

screenshot_03.png

转换过程中我踩过几个坑:

  • YAML 会把 yesnoonoff 解析成布尔值,再转回 JSON 可能变成 true/false
  • 数组转 CSV 时,对象数组会被拍平成多行,字段值本身是对象或数组时会被写成 [object Object]
  • XML 转换会用 @_ 前缀表示属性,后续处理时要注意。

我现在基本统一在 JSON 里编辑配置,确认无误后再转成 YAML,比直接手写 YAML 少了缩进出错的概率。

七、用 JSONPath 提取字段

大 JSON 只看结构还不够,有时候我只想要其中几个字段。我会把格式化后的 JSON 复制到 JSONPath 查询工具里,用表达式直接提取:

$.store.book[?(@.price < 10)].title
  • $..author:提取所有层级的 author 字段;
  • $.users[0,2]:只取第 1 和第 3 个用户;
  • $.data.items[*].id:提取列表里每个对象的 id
  • $..[?(@.status == 'active')].name:按条件筛选状态为 active 的对象名称。

screenshot_06.png

对于接口返回列表的场景,这个组合能帮我快速确认字段类型和数据样例。写 TypeScript 类型之前,我会先用 JSONPath 抓几条典型记录,避免被空数组或缺失字段误导。

八、从 JSON 到实体类类型定义

确认字段结构后,我通常会把 JSON 示例贴到“JSON 转多语言实体类”工具里,选择 TypeScript,一键生成接口类型:

interface User {
  id: number;
  name: string;
  email?: string;
  roles: string[];
}

screenshot_07.png

生成结果通常还需要手动调整:把可能为空的字段标成可选,给枚举字段加注释,把嵌套对象抽成独立接口。但比起从零手写,已经能省掉大量重复劳动。我最近在补一个老项目的类型定义,就靠这种先抓响应、再生成接口的方式,把接口文档滞后的问题缓解了不少。

九、数字精度、日期与特殊值

JSON 对数据类型的支持很有限。比如后端返回了一个 19 位的订单号,JSON.parse 后会因为 JavaScript 数字精度问题而失真。遇到这种情况,要么让后端改成字符串返回,要么在前端用 json-bigint 这样的库解析。

{
  "orderId": 1234567890123456789
}

日期在 JSON 里通常以 ISO 8601 字符串形式存在,格式化工具不会自动转 Date,需要我在代码里 new Date()NaNInfinity-Infinity 不是合法的 JSON 值,但 JavaScript 对象里可能有,直接 JSON.stringify 会返回 null,这一点在序列化业务数据时要特别小心。

我现在的做法是:先把原始响应丢进格式化工具确认字段类型,再在生成实体类时把长整型标成 string,把日期标成 string | Date

十、一条完整的工作流

把这些功能串起来,我现在的典型工作流是:

  1. 从接口文档或日志复制原始 JSON,大文件优先上传;
  2. 丢进格式转换器校验并格式化,顺手排序键;
  3. 用树形视图确认深层结构;
  4. 如有嵌套转义,先处理成可读 JSON;
  5. 用 JSONPath 提取关键字段样本;
  6. 把样本转成 TypeScript 接口或 YAML 配置;
  7. 最后检查一遍长整型和日期字段的类型定义。

这套流程基本覆盖了接口联调、配置管理、类型补全三个高频场景。以前完成这些步骤可能要开两三个工具或写临时脚本,现在一个页面里就能搞定大部分。

十一、写在最后

以前我觉得 JSON 格式化工具就是把代码变好看,最近因为频繁联调和看第三方文档,才发现校验行号、键排序、转义/去转义、树形视图这些“小功能”在真实工作流里很能救命。工具派的 JSON 数据格式转换我最近用得比较多,顺手记录一下,希望能给同样被 JSON 折磨的同学省几分钟。