JSON 在线格式化到底在改什么:压缩、转义与 7 个常见报错

接口返回一坨没有换行的 JSON,想做一次 JSON 在线格式化看清楚结构,结果第一行就红字报 Unexpected token —— 这时候你要的不是「美化」,而是先搞清它为什么解析不了。

格式化、压缩、转义被混为一谈,是排错慢的最大原因:它们的前提条件完全不同。

一、格式化、压缩、转义是三件事

操作 输入 → 输出 数据本身变没变 典型用途
格式化(美化) 合法 JSON → 带缩进换行的 JSON 值不变,只加空白 人眼查看、比对 diff
压缩(minify) 合法 JSON → 去掉所有空白的单行 值不变,只删空白 传输、写进配置、贴进 curl
转义 任意文本 → 能塞进字符串字面量的文本 变了,加反斜杠 把 JSON 当字符串塞进 shell / Java 代码
反转义 字符串字面量 → 原文 变了 从日志里还原请求 body

关键区别:格式化和压缩的前提是这段文本本来就是合法 JSON,它们只重排空白;转义是纯字符替换,不校验合法性。所以「格式化失败」说明你的数据不是合法 JSON,「转义失败」多半是反斜杠已经乱了,两者排查方向不一样。

顺带说一个容易被忽略的用途:格式化真正的价值不在好看,而在统一格式后再比对。两份内容完全相同的配置,一个用 2 空格一个用 Tab,diff 会显示全部行变更;先按同一缩进格式化、再排序 key,diff 出来的才是真实差异。

二、7 个高频报错,先看表

报错 / 现象 根因 怎么修
Unexpected token o in JSON at position 1 拿到的是 [object Object],对象没 stringify 就拼进字符串 JSON.stringify(obj)
Unexpected token ' in JSON at position 1 用了单引号,JSON 只允许双引号 单引号换双引号
Unexpected token } in JSON at position N 尾随逗号 删掉最后一项后面的逗号
Unexpected token < in JSON at position 0 返回的是 HTML(404 页 / 网关错误页) 先看原始响应体,别直接 parse
Unexpected token  in JSON at position 0 文件带 UTF-8 BOM 见第五节
jsonp123({...}) is not valid JSON 拿到的是 JSONP,不是 JSON 剥掉外层函数调用
粘进去页面直接假死 JSON 太大,浏览器主线程扛不住 见第七节

三、三个值得单独展开的

单引号不是合法 JSON。 JS 对象字面量和 Python 的 dict 都容忍单引号,JSON 标准(RFC 8259)不允许。从 Python 那边 str(dict) 出来的字符串经常三种错一起犯:

{'ok': True, 'msg': None}     // ❌ 单引号 + True + None
{"ok": true, "msg": null}     // ✅

让 Python 那边改成 json.dumps(d, ensure_ascii=False),别手改。

尾随逗号。 JSON 不允许对象或数组最后一项后面有逗号,JS 里却完全合法:

{"a":1,"b":2,}   // ❌
{"a":1,"b":2}    // ✅

注意报错里的 position 通常指向那个右括号而不是逗号本身,往左看一个字符就找到了。

JSONP 不是 JSON。 你拿到的是 jsonp123({"code":0,"data":[]});,真正要的是里面那一段。剥掉外层函数名和分号即可,标准的格式化工具识别不了它,因为它在语法上是一段 JS 调用表达式。

转义的真实场景。 转义不是给 JSON 用的,是给「把 JSON 塞进另一种语言的字符串」用的。比如要把一段 body 拼进 curl -d、写进 Java 的 String body = "..."、或者作为环境变量传进容器,中间的每一个双引号和换行都得变成 \"\n,否则外层语言会提前截断字符串。反过来,从日志里复制出来的请求体往往已经被转义过一次,先反转义再看,别直接拿去 parse。

四、命令行与代码里怎么做(可直接复制)

# curl + jq:格式化接口返回
curl -s https://api.example.com/v1/users | jq '.'

# jq -c 压缩成一行
curl -s https://api.example.com/v1/users | jq -c '.'

# jq -S 按 key 排序输出,比对两份配置前必做
jq -S '.' a.json > a.sorted.json

# 只校验,不看内容(-e 失败时返回非 0)
jq -e '.' a.json > /dev/null && echo OK
# Python:json.tool 就是命令行版的格式化工具
python3 -m json.tool input.json --indent 2 --sort-keys

# 批量找出哪个文件坏了
for f in *.json; do python3 -m json.tool "$f" > /dev/null || echo "坏文件: $f"; done
// Node:JSON.stringify 的第三个参数是缩进
console.log(JSON.stringify(data, null, 2))     // 2 空格缩进
console.log(JSON.stringify(data, null, '\t'))  // Tab 缩进
console.log(JSON.stringify(data))              // 不传 = 压缩成一行

// 第二个参数 replacer 配合递归,实现 key 排序
const sortKeys = (v) =>
  v && typeof v === 'object' && !Array.isArray(v)
    ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, sortKeys(v[k])]))
    : v
console.log(JSON.stringify(sortKeys(data), null, 2))

JSON.stringify(v, null, space) 的 space 传数字就是空格数(上限 10),传字符串就用该字符串缩进(上限 10 字符)。它不会帮你排序 key,输出顺序保持对象的插入顺序 —— 这是很多人以为「格式化会顺便排序」的误区。

五、BOM:后端返回带 BOM,前端 parse 必挂

UTF-8 BOM 是文件开头的三个字节 EF BB BF,Windows 记事本、Excel 导出、部分老版本 Java / PHP 输出流会带上它。肉眼看不见,但 JSON.parse 会在第 0 位撞上它:

const text = await res.text()            // "\uFEFF{\"code\":0}"
JSON.parse(text)                         // ❌ Unexpected token  in JSON at position 0

JSON.parse(text.replace(/^\uFEFF/, ''))  // ✅ 先剥掉 BOM

排查:curl -s https://api.example.com/x | xxd | head -1,看前三个字节是不是 efbbbf。根治在服务端 —— 写文件用无 BOM 的 UTF-8,响应头带 Content-Type: application/json; charset=utf-8

有意思的是,导出 CSV 给 Excel 打开时反而要主动加 BOM(Excel 靠 BOM 认 UTF-8,否则中文全乱码)。这两件事经常出现在同一个项目里,别记反了。

六、JSON5 和 JSONL 是什么

名字 是什么 能被 JSON.parse 直接吃吗
JSON RFC 8259 标准格式
JSON5 超集:允许注释、单引号、尾随逗号、无引号 key ❌ 需要 json5 库
JSONL / NDJSON 一行一个 JSON,多行组成一个文件 ❌ 要按行逐个 parse

JSON5 主要用来写人维护的配置文件;JSONL 常见于日志和大批量导出(每行独立,可流式读取,坏一行不影响其他行)。

# JSONL 逐行校验,找出坏的那一行
n=0; while IFS= read -r line; do
  n=$((n+1))
  printf '%s' "$line" | jq -e . >/dev/null 2>&1 || echo "第 $n 行不合法"
done < data.jsonl

转成标准 JSON 之后要看结构、要缩进、要转义,或者要转成 CSV / XML / YAML 给别的系统用,直接用工具更快:

👉 JSON 在线格式化(免登录、不上传)

七、浏览器 console 里几个省事用法

不想在编辑器和浏览器之间来回倒腾的话,DevTools 本身就能干不少事:

// 1. 把接口返回的对象格式化后直接复制到剪贴板
await fetch('/api/users').then((r) => r.json()).then((d) => copy(JSON.stringify(d, null, 2)))

// 2. 拿到响应体原文(能看出有没有 BOM / 是不是 HTML)
const text = await (await fetch('/api/users')).text()
console.log(text.charCodeAt(0))        // 65279 就是有 BOM
JSON.parse(text.replace(/^\uFEFF/, ''))

// 3. 试错:报错信息里的 position 直接定位
JSON.parse(badText)                    // 看 position N,再从 N-20 开始截一段出来看
badText.slice(N - 20, N + 20)

// 4. 只想看某个字段,不用展开整个对象树
console.table(data.list.slice(0, 20))  // 数组直接出表格

Network 面板里选中请求 → Response 右键「Copy response」拿到的就是原始文本,粘进在线工具比从 console 里复制对象靠谱(后者经常被二次转义)。

八、巨大 JSON 卡死编辑器:在线格式化工具也救不了

一个 200MB 的 JSON 粘进浏览器,JSON.parse 加语法高亮全在主线程跑,基本必卡。超过 10MB 就别粘贴了,改用命令行:

jq '.data[0:10]' big.json                          # 只取前 10 条出来看
jq 'paths(scalars) | join(".")' big.json | sort -u | head -50   # 列出所有字段路径
jq -e '.' big.json > /dev/null && echo OK          # 只确认合不合法

九、排错清单(按顺序过一遍)

步骤 动作 通过标准
1 确认拿到的是响应体原文 开头是 {[,不是 [object Object]
2 去掉首尾空白和 BOM text.charCodeAt(0) 不等于 65279
3 排除 JSONP / HTML 包裹 结尾没有 );,没有 </html>
4 引号统一、字面量归一 没有 '、没有 True / None / undefined
5 检查尾随逗号和注释 没有 ,} / ,] / //
6 jq -e . 或在线工具校验 提示「格式合法」
7 比对两份 JSON 前先 jq -S 排序 diff 里不再全是顺序差异
8 超过 10MB 走命令行 页面不再假死

十、速查表

jq '.' f.json                 # 格式化
jq -c '.' f.json              # 压缩成一行
jq -S '.' f.json              # key 排序后输出
jq -e '.' f.json > /dev/null  # 只校验
python3 -m json.tool f.json --indent 2 --sort-keys
node -e 'console.log(JSON.stringify(require("./f.json"),null,2))'

JSON.stringify(v, null, 2) 第三个参数管缩进,第二个参数 replacer 管过滤和排序 —— 记住这两句,格式化这件事就不用再翻文档了。


相关工具:JSON 格式化JSON 转 CSV/ExcelBase64 编码解码

全部纯前端实现,输入的内容不上传服务器。

还有 53 个免费在线工具

纯前端实现,不用注册,数据不上传服务器。

浏览全部工具