Python JSONDecodeError 排查:NaN、不完整结构、数字字符串的常见坑

JSONDecodeError 定位

json.loads() 报错时,JSONDecodeError 对象携带精确位置信息:

import json

try:
    data = json.loads(content)
except json.JSONDecodeError as e:
    print(f"错误信息: {e.msg}")
    print(f"行号: {e.lineno}, 列号: {e.colno}")
    print(f"字符偏移: {e.pos}")
    print(f"出错内容: {content[max(0, e.pos-50):e.pos+50]}")

原因一:裸 NaN / Infinity

JSON 规范不允许 NaNInfinity 作为数值字面量,只有加了引号的字符串 "NaN" 才合法:

# 合法
json.loads('{"value": "NaN"}')      # OK,返回字符串

# 非法
json.loads('{"value": NaN}')        # JSONDecodeError: Expecting value
json.loads('{"value": Infinity}')   # JSONDecodeError

如果数据源返回了裸 NaN,需要预处理:

import re

cleaned = re.sub(r'\bNaN\b', '"NaN"', content)
data = json.loads(cleaned)

原因二:内容不是完整 JSON

常见错误是将多个 JSON 对象直接拼接(不用数组包裹):

# 非法:两个对象并排
content = '{"a": 1}, {"b": 2}'
json.loads(content)  # JSONDecodeError

# 合法:用数组包裹
content = '[{"a": 1}, {"b": 2}]'
json.loads(content)  # OK

另一种情况是只收到了响应的一部分(网络截断),pos 错误位置通常在字符串末尾。

原因三:数字存为字符串

JSON 中数字加了引号,解析后得到字符串而非数值:

{"x": "1228.8650628969294", "z": "NaN"}
data = json.loads(content)
type(data["x"])   # str,不是 float

# 需要手动转换
x = float(data["x"])

# z 是 "NaN" 字符串,转 float 得到 nan
z = float(data["z"])   # float('nan')
import math
math.isnan(z)  # True

原因四:编码问题

非 UTF-8 编码的文件用 json.loads 读取会报错:

# 读取文件时指定编码
with open('data.json', 'r', encoding='utf-8') as f:
    data = json.load(f)

# 如果来源是 bytes
data = json.loads(raw_bytes.decode('utf-8'))

快速调试模板

import json, traceback

def safe_parse(content: str):
    try:
        return json.loads(content), None
    except json.JSONDecodeError as e:
        context_start = max(0, e.pos - 30)
        context_end = min(len(content), e.pos + 30)
        snippet = content[context_start:context_end]
        marker = ' ' * (e.pos - context_start) + '^'
        print(f"JSONDecodeError at line {e.lineno}, col {e.colno}:")
        print(snippet)
        print(marker)
        return None, e