JSONDecodeError 多因编码或隐藏字符导致,非语法错误;应检查BOM、控制符、编码,并预清洗数据、记录上下文、校验结构。

JSONDecodeError 通常不是数据问题,而是编码或空白字符惹的祸
绝大多数 JSONDecodeError 并非因为 JSON 格式“写错了”,而是读取时混入了 BOM、不可见控制字符(如 \x00、\ufeff)、换行符错位,或文件实际是 GBK 编码却用 UTF-8 打开。直接在 json.loads() 外套 try/except 只能捕获异常,无法定位根因。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 先用
open(file, "rb").read()检查原始字节,看是否有b"\xef\xbb\xbf"(BOM)或零字节 - 用
repr(data_str)打印字符串,比print(data_str)更容易发现隐藏字符 - 读文件时显式指定
encoding="utf-8-sig",自动剥离 UTF-8 BOM - 若怀疑编码问题,先试
chardet.detect(raw_bytes)再解码,不要硬扛
用 json.loads() 前做轻量预清洗,比层层 try/except 更可靠
对不可信输入(如 HTTP 响应体、用户上传文本),在解析前做最小化清洗,能避免 80% 的 JSONDecodeError。这不是妥协,而是承认现实输入永远不标准。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
data.strip()去首尾空白,防止换行或空格导致开头不是{或[ - 用正则
re.sub(r"//.*?$|/\*.*?\*/", "", data, flags=re.MULTILINE|re.DOTALL)去掉注释(仅限调试或内部系统,标准 JSON 不支持) - 对已知含 Windows 行尾的文本,用
data.replace("\r\n", "\n")统一换行 - 避免用
json.load()直接读网络流;先response.text或response.content.decode("utf-8-sig"),再传给json.loads()
捕获 JSONDecodeError 时必须记录原始输入片段
只打印 e.msg 和 e.lineno 没用——你不知道出错位置上下文是什么。没有上下文,就等于没日志。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 捕获时取原始字符串的出错行附近 2–3 行:
lines = data.splitlines(); context = lines[max(0, e.lineno-2):e.lineno+1] - 用
json.JSONDecoder().raw_decode(data)替代loads(),它返回 (obj, endpos),可定位解析停在哪——适合流式或拼接场景 - 生产环境别用
str(e)记日志;至少带repr(data[max(0,e.pos-20):e.pos+20])截取错误点前后字符 - 如果输入来自 API,把
response.headers.get("content-type")和len(response.content)也记下来,常能暴露 gzip 未解压或响应截断
遇到嵌套结构或部分字段缺失,别硬刚 JSONDecodeError
有时你以为是解析失败,其实是数据本身字段类型突变(比如 "count": null 突然变成 "count": "N/A"),或者前端发来的是 {...},后端期待 [{...}]。这类问题 JSONDecodeError 根本不会抛,但后续逻辑崩了才暴露。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
jsonschema或 Pydantic 的BaseModel.parse_raw()做结构校验,比靠异常更早发现问题 - 对关键字段加防御性检查:
if isinstance(data.get("items"), list): ... else: raise ValueError("expected 'items' to be list") - 用
orjson替代标准json:它更快,且orjson.loads()对 NaN/Infinity 报错更明确,不静默转成None - 若需兼容弱类型,先用
ast.literal_eval()尝试(仅限可信输入),它比json.loads()更容忍单引号和末尾逗号
repr(),少半天排查。


















