json.JSONDecoder 如何自定义处理 NaN / Infinity / -Infinity

9次阅读

jsONDecoder默认不支持NaN和Infinity,因json标准禁止这些值;可通过object_hook或parse_Float(需strict=False)实现兼容解析,但编码时仍需自定义处理。

json.JSONDecoder 如何自定义处理 NaN / Infinity / -Infinity

JSONDecoder 默认不支持 NaN 和 Infinity

pythonjson.JSONDecoder 在解析时遇到 NaNInfinity-Infinity 会直接报错:ValueError: Invalid value encountered: NaN(或类似提示)。这不是 bug,而是 JSON 标准本身禁止这些值 —— 它们不属于 RFC 7159 合法的 JSON 数字。

但实际中,不少后端(尤其是 javaScript 环境)会输出带这些值的“类 JSON”字符串。若你必须接收并解析这类输入,就得绕过标准校验。

用 object_hook + float() 强转是最简方案

核心思路:在解析完每个对象(dict)后,遍历其所有字符串值,对匹配 "NaN""Infinity""-Infinity" 的项尝试用 float() 转换。Python 的 float() 原生支持这些字面量。

示例实现:

import json 

def handle_nan_inf(obj): for key, value in obj.items(): if isinstance(value, str): if value in ("NaN", "Infinity", "-Infinity"): obj[key] = float(value) return obj

data = '{"x": "NaN", "y": "Infinity", "z": 42}' result = json.loads(data, object_hook=handle_nan_inf)

result == {'x': nan, 'y': inf, 'z': 42}

  • 仅作用于 dict 层级,不会递归处理嵌套 list 中的字符串;如需全覆盖,改用 object_hook + 深度遍历,或换用 parse_float
  • 注意 float("NaN") 返回的是 float('nan'),它不等于自身(nan == nanFalse),后续判断要用 math.isnan()
  • 此方式不改变原始 JSON 字符串结构,只在解析后做一次修正,开销小

用 parse_float 捕获所有数字字面量(含非法格式)

parse_float 参数会在 JSON 解析器遇到任何数字 Token 时调用,包括那些本该失败的 NaN 字符串 —— 只要它们没被预校验拦住。关键在于:**必须配合 strict=False**,否则解析器在 tokenize 阶段就抛错了。

示例:

import json 

def robust_float(s): try: return float(s) except ValueError:

可选:记录未知格式,或返回 None/0

    return float('nan')

data = '{"a": NaN, "b": Infinity}' result = json.loads(data, parse_float=robust_float, strict=False)

result == {'a': nan, 'b': inf}

  • strict=False 是前提,否则 parse_float 根本不会被触发
  • parse_float 接收的是原始 token 字符串(如 "NaN"),不是已解析的数字,所以能捕获非法字面量
  • 它对所有数字位置生效(dict 值、list 元素、嵌套任意深度),比 object_hook 更彻底
  • 副作用:也会处理合法数字字符串(如 "123"),若你的 robust_float 有额外逻辑,需先判断是否为特殊字面量

别忽略 JSONEncoder 的反向兼容问题

能解析 NaN 不代表能原样输出。Python 的 json.JSONEncoder 默认拒绝编码 float('nan'),会报 ValueError: Out of range float values are not JSON compliant

  • 若需导出,必须自定义 default继承 JSONEncoder,例如:default=Lambda x: None if math.isnan(x) else x
  • 更常见做法是提前清洗数据:入库/传输前把 nan/inf 替换为 None 或特定哨兵值(如 "__NaN__"),避免序列化阶段出问题
  • 后端约定比强行兼容更可靠 —— 比如统一用 NULL 表示缺失,用字符串 "inf" 表示无穷大

真正麻烦的从来不是怎么读进来,而是读进来之后,要不要、以及能不能再安全地写出去。

text=ZqhQzanResources