json.loads 抛 JSONDecodeError 时,异常信息里有行号、列号、字节偏移量——先把这三个印出来,问题基本就定位了:
import json
try:
data = json.loads(content)
except json.JSONDecodeError as e:
print(e)
print("line:", e.lineno, "col:", e.colno, "pos:", e.pos)
print(content[max(0, e.pos - 40): e.pos + 40])
最后一行把出错位置前后 40 个字符打出来,肉眼就能看出问题。以下是几种常见踩坑。
1. 根本不是合法 JSON(最常见)
抓了一段”对象序列”贴进来:
{
"id": 1
},
{
"id": 2
}
这不是合法 JSON,缺个数组外壳。要么改成:
[
{ "id": 1 },
{ "id": 2 }
]
要么按字段包一层:
{
"objects": [...]
}
2. NaN / Infinity 没引号
标准 JSON 不支持 NaN、Infinity。这样合法:
{"z": "NaN"} ← 字符串
这样非法:
{"z": NaN} ← Python json.loads 会崩
服务端如果是 Python json.dumps(..., allow_nan=True) 输出的,可能就是裸 NaN。解决办法:
import json
data = json.loads(content, parse_constant=lambda x: None)
# 或
data = content.replace("NaN", "null")
data = json.loads(data)
3. 结尾多逗号
也是非标准 JSON:
{
"a": 1,
"b": 2, ← 这个逗号 Python 不接受
}
用 json5 或者预处理正则去掉最后一个逗号:
import re
content = re.sub(r",\s*([}\]])", r"\1", content)
4. UTF-8 BOM
从 Windows 记事本另存的 JSON 常带 BOM,第一个字符是 :
json.loads(content)
# JSONDecodeError: Expecting value: line 1 column 1 (char 0)
读文件时用 utf-8-sig:
with open("data.json", encoding="utf-8-sig") as f:
data = json.load(f)
5. 数字全被当字符串了
不会抛异常,但会让下游算错:
{"x": "1228.86"}
type(obj["x"]) 是 str,要计算得先转:
x = float(obj["x"])
批量转的话用一个小函数:
def to_float_fields(d, fields):
for f in fields:
if f in d:
d[f] = float(d[f])
return d
6. 单引号不是 JSON
从 Python print(dict) 复制的字符串是单引号:
{'id': 1, 'name': 'Tom'}
这不是 JSON,是 Python repr。要么请对方 json.dumps 输出,要么本地 ast.literal_eval:
import ast
data = ast.literal_eval("{'id': 1, 'name': 'Tom'}")
literal_eval 只解析字面量,比 eval 安全得多。
一句话总结
JSONDecodeError 第一步永远是把 e.pos 附近的原文打出来。九成情况能在 30 秒内定位到具体字符是啥。
