← 返回博客首页

JSON 格式化与验证工具使用教程:语法、校验、Schema 与大文件处理

为什么 JSON 成了默认选项

JSON 能赢不是因为它设计得最优雅,而是因为它在「够用」和「简单」之间卡在了正确的位置。对比一下三个主流交换格式:

维度 JSON XML YAML
体积 小(无冗余标签) 大(成对标签) 最小
数据类型 6 种,够用 需 XSD 定义 丰富,含类型推断
注释 ❌ 不支持
解析成本 极低(语言原生) 高(DOM/SAX) 中(缩进敏感)
人写友好度 中(括号易错)
主要地盘 Web API、配置、日志 企业/金融遗留系统 K8s、CI、IaC

结论:机器与机器之间高频交换用 JSON,人手工维护的配置文件用 YAML。这也是为什么 Kubernetes 的 manifests 是 YAML,而它的 API 通信是 JSON。

需要随时对照数据类型与转义规则时,本站的 JSON 速查表 可以直接开在旁边。

JSON 语法:严格在哪里

JSON 的全部语法规则加起来只有几条,但每一条都是硬性的,没有容错空间。

六条硬规则

  1. 键名必须用双引号"key",不能是 key'key'
  2. 字符串值必须用双引号,不能用单引号或反引号
  3. 不允许尾随逗号:最后一个元素后面不能留逗号
  4. 不支持注释///* */ 都会导致解析失败
  5. 必须使用 UTF-8 编码(RFC 8259 的强制要求)
  6. 顶层可以是对象、数组、字符串、数字、true/false/null——不限于对象

六种数据类型

类型 示例 注意点
string "hello" 必须双引号;控制字符要转义
number 423.141e10 不支持 NaNInfinity、前导 0、十六进制
boolean true / false 只能小写
object {"a": 1} 键必须是字符串
array [1, 2, 3] 元素类型可混
null null 不是 undefined、不是 NULL

合法 / 非法对照

写法 判定 原因
{"a": 1}
{a: 1} 键名未加引号
{'a': 1} 单引号
{"a": 1,} 尾随逗号
{"a": 01} 前导零
{"a": NaN} JSON 无此字面量
{"a": undefined} 无此类型
[1, "2", null] 数组元素可混类型

格式化与压缩:不是审美问题

压缩能省多少

去掉所有缩进与换行,实测通常减少 30–50% 体积;如果键名重复度高(如一万条同类记录),配合 gzip/Brotli 后差距会进一步拉大。反过来,一个 10MB 的 JSON 压缩后能省 3–5MB 传输量——在移动端弱网环境下这就是几秒的差距。

需要压的时候直接用 JSON 压缩工具,比手写正则删空白可靠(正则会误伤字符串内部的空格)。

缩进约定

风格 使用场景
2 空格 最主流,JS/TS 生态默认(Prettier、ESLint)
4 空格 Python 生态、部分 Java 项目
Tab 少见,但与 YAML 不兼容,不推荐

关键是同一仓库内保持一致,差异比选择本身更伤。

两个反直觉的注意点

  • 格式化之后再签名会验签失败。HMAC、数字签名、JWT 的计算对象是原始字节序列,多一个空格结果就完全不同。
  • 生产日志不要打印格式化后的大对象。应打印压缩版,并按长度截断,否则日志体积和写入耗时都会失控。

验证:把错误挡在运行时之前

JSON.parse 抛出的错误往往只有一句 Unexpected token } in JSON at position 1234,看起来毫无信息量。掌握分布就能秒定位。

五类高频错误

错误类型 典型报错 真实原因
尾随逗号 Unexpected token } 对象/数组最后一项后多写了逗号
单引号 Unexpected token ' 从 JS 字面量复制过来
括号不闭合 Unexpected end of JSON input 少了 }],报在文件末尾
注释 Unexpected token / 手写了 // 说明
BOM 头 Unexpected token 在 position 0 文件以  开头

定位心法

报错位置往前看,不要往后看。 解析器是在遇到「无法继续的字符」时才报错的,而笔误通常在它前面几行。具体到症状:

  • 报错在末尾 → 几乎一定是括号不闭合,从外往内数配对
  • 报错指向逗号 → 通常是多写了尾随逗号,而不是少写内容
  • 报错在 position 0 → 先查 BOM 和编码
  • 报错在中间某行 → 看这一行的上一行末尾

把原始内容粘进 JSON 格式化工具 是最快的一步,多数实现会直接标出出错行列号。

JSON Schema:把结构变成可执行的契约

验证「是不是合法 JSON」只解决了语法问题;验证「字段对不对、类型对不对」要靠 JSON Schema。

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["id", "email", "createdAt"],
  "additionalProperties": false,
  "properties": {
    "id": { "type": "string", "format": "uuid" },
    "email": { "type": "string", "format": "email" },
    "age": { "type": "integer", "minimum": 0, "maximum": 150 },
    "status": { "enum": ["active", "suspended", "deleted"] },
    "createdAt": { "type": "string", "format": "date-time" }
  }
}

常用关键字

关键字 作用
type 类型约束,可给数组如 ["string", "null"]
required 必填字段列表
enum 枚举允许值
minimum / maximum 数值范围
minLength / maxLength 字符串长度
pattern 正则匹配(配合 正则测试工具 调试)
additionalProperties 设为 false 可拒绝未声明字段
format 语义格式(email/uri/uuid/date-time 等)

从真实响应反推 schema

手写 schema 容易和真实数据漂移。更稳的做法是拿一份真实响应体生成初版,再人工收紧约束——JSON Schema 生成工具 就是干这个的。生成后把 additionalProperties 收紧、把可以为空的字段标注清楚,才算一份能用的契约。

在 CI 里用 ajv 跑一遍,API 改动导致的结构破坏会在合并前暴露。

大文件与流式处理

内存不是「文件多大就占多大」

这是最容易误判的一点:JSON 解析后在内存里的占用通常是原始文本的 3–10 倍。因为每个键和值都会变成独立对象/字符串,附带指针与元数据开销。一个 100MB 的 JSON 文件,直接 JSON.parse 很可能需要 500MB–1GB 内存。

文件规模 建议做法
< 10 MB 直接 JSON.parse,不必优化
10–100 MB 流式解析,或按批次切片处理
> 100 MB 必须流式,且考虑换行分隔的 JSONL 格式
> 1 GB 改用专门的列式/二进制格式(Parquet、Arrow)

流式解析器

语言 特点
simdjson C++ / 多语言绑定 利用 SIMD 指令,解析可达 GB/s 级
ijson Python 惰性迭代,边读边产出
JSONStream Node.js 按 JSONPath 增量抽取
serde_json (StreamDeserializer) Rust 零拷贝友好

抽取字段用 JSONPath

如果只需要大文件里的某几个字段,不必解析整棵树——用 JSONPath 表达式直接定位,边解析边丢弃无关分支。表达式写法可以在 JSONPath 查询工具 上对着真实数据调。

结构对比:改完之后到底变了什么

改配置、升级接口之后,肉眼比对两个 JSON 几乎必然出错。正确做法是对比解析后的结构而非文本:

  • 键的增删 → 关注 required 与兼容性
  • 值的变化 → 关注类型是否漂移("1"1
  • 数组顺序变化 → 判断业务上是否有序语义

JSON Diff 给的是结构化差异,比 diff 命令的文本行差异可靠得多(后者会被格式化差异淹没)。

安全:解析 JSON 的四个真实风险

风险 危害 修法
eval() / new Function() 解析 远程代码执行,直接沦陷 一律用 JSON.parse
原型链污染 深合并后篡改所有对象原型 合并前剔除 __proto__/constructor/prototype
深度嵌套爆栈 递归解析器栈溢出,服务拒绝 解析前做深度上限校验
大整数精度丢失 金额/订单号静默变成错误值 超过 2^53-1 的字段用字符串传输

第 2 条最隐蔽:

const payload = JSON.parse('{"__proto__":{"isAdmin":true}}');
const config = {};
merge(config, payload);           // 危险:污染 Object.prototype
console.log({}.isAdmin);          // true —— 全站对象都被改了

第 4 条在金融、订单类接口里最致命:9007199254740993 解析后变成 9007199254740992,且不会报错

常见任务速查表

任务 做法 工具
读懂压缩成一行的 JSON 格式化(2 空格缩进) JSON 格式化
排查解析报错 粘进去看行列号 JSON 格式化
缩小传输体积 压缩去空白 JSON 压缩
折叠浏览深层结构 树形查看器 JSON 查看器
对比两版配置差异 结构化 diff JSON Diff
从响应反推 schema 自动生成 + 人工收紧 JSON Schema 生成
从大文件抽字段 JSONPath 表达式 JSONPath 查询
调试字段级正则 先验证 pattern 正则测试
广告

常见问题

JSON 字符串到底能不能用单引号?

**不能**。JSON 规范要求键名和字符串值**必须**用双引号包裹,单引号、反引号或不加引号都不合法。这是从 JavaScript 对象字面量迁移过来时最常见的错误——`{ name: 'Alice' }` 在 JS 里是合法对象,但作为 JSON 解析时会直接抛错。同理,**末尾不能有多余逗号**:`{ "a": 1, }` 在 JS 里合法,在 JSON 里非法。**JSON 也不是 YAML**:不要因为 YAML 支持注释就往 JSON 里写 `//` 或 `/* */`,标准 JSON 解析器一律报错。

JSON 解析失败了,报错只给一个行列号,怎么快速定位?

**从报错位置往前看,而不是往后。** 解析器在遇到**第一个无法继续的字符**时才报错,而真正的笔误通常在它**前面几行**。实战步骤:① 把原始字符串丢进 [JSON 格式化工具](/json-formatter.html),多数工具会直接标出出错行列;② 若报错在文件末尾,几乎一定是**括号不闭合**,从外往内数一遍 `{}` 与 `[]` 的配对;③ 报错指向某个逗号,通常是**多写了一个尾随逗号**,而不是少写了内容;④ 中文场景额外检查:文件是否带 **BOM**(`\uFEFF` 开头会让 `JSON.parse` 直接失败)、编码是否 UTF-8。

什么时候该压缩 JSON,什么时候该格式化?

**传输用压缩,人读用格式化,两者不要混。** 线上的 API 响应、配置文件、消息体都应该压缩(去掉所有缩进与换行),实测能减少 **30–50%** 体积;本地调试、代码评审、日志排查时再格式化。两个容易被忽略的点:**① 不要格式化后再做签名**——数字签名、HMAC、JWT 的计算对象是**原始字节**,格式化会改变字节序列导致验签失败;② 生产日志里直接打印格式化后的大对象会显著拖慢写入、撑爆日志配额,应打印压缩版并按长度截断。压完之后想确认没改坏语义,可以用 [JSON Diff](/json-diff.html) 对比压缩前后解析出的结构是否一致。

解析不可信来源的 JSON 有哪些真实安全风险?

**四个,按危险程度排序:① 用 `eval()` 或 `new Function()` 解析**——等于把对方的字符串当代码执行,可直接沦陷,必须永远用 `JSON.parse`;**② 原型链污染**——`JSON.parse('{"__proto__":{"isAdmin":true}}')` 会得到一个带 `__proto__` 键的普通对象,一旦被深合并(`Object.assign`、lodash `merge`)进配置,就可能篡改所有对象的原型,修法是合并前过滤 `__proto__`/`constructor`/`prototype` 三个键;**③ 深度嵌套导致栈溢出**——恶意构造十万层 `[[[[...]]]]` 会让递归解析器爆栈,应对外层做深度上限校验;**④ 超大数字精度丢失**——`JSON.parse` 会把超过 `Number.MAX_SAFE_INTEGER` 的整数转成浮点数,金额、订单号这类字段必须用字符串传输。

← 返回博客首页