Python JSON 序列化的边界与坑点

19次阅读

datetime 等类型需用 default 参数处理,NaN/Infinity 需 allow_nan=False 校验,中文需 ensure_ascii=False,自定义类应实现 to_dict 方法。

Python JSON 序列化的边界与坑点

datetime 对象直接 json.dumps 会报错

Python 的 json.dumps 默认不支持 datetimedatebytes 等类型,遇到就抛 TypeError: Object of type datetime is not JSON serializable

常见场景是数据库查询结果含时间字段,或日志结构里带 datetime.now(),一序列化就崩。

  • 最轻量解法:用 default 参数传处理函数,比如 lambda obj: obj.isoformat() if isinstance(obj, (datetime, date)) else None
  • 注意 default 函数必须覆盖所有非标类型,否则仍报错;返回 None 会导致字段消失,不是万能兜底
  • datetime 带时区(tzinfo)时,isoformat() 输出含 +08:00,但某些老系统只认 UTC 或无时区格式,得手动转成 obj.astimezone(timezone.utc).isoformat()

NaN、Infinity 在 JSON 中非法但 Python 允许 float 表示

JSON 规范明确禁止 NaNInfinity-Infinity,但 Python 的 float 可以表示它们,json.dumps 默认会静默转成 null(不报错!),极易埋雷。

典型触发点:NumPy 计算结果、pandas DataFrame 含空值运算、除零后未检查。

立即学习Python 免费学习笔记(深入)”;

  • allow_nan=False 强制报错:json.dumps(data, allow_nan=False),这样能早暴露问题
  • 如果必须保留语义,可预处理:遍历结构,把 math.isnan(x)math.isinf(x) 的值替换成字符串 "NaN"null,再序列化
  • 注意 allow_nan 是开关,不是转换器——设为 False 才真正校验,设为 True(默认)反而放行非法值

中文字符被 uXXXX 转义,前端 显示乱码

默认情况下 json.dumps 会对非 ASCII 字符做 Unicode 转义,比如 "你好" 变成 "u4f60u597d",虽然合法,但可读性差,某些调试 工具 或旧版 浏览器 解析异常。

这不是 bug,是默认行为,源于早期对传输安全的保守设计。

  • 加参数 ensure_ascii=False 即可输出原生中文:json.dumps(data, ensure_ascii=False)
  • 但要注意 HTTP 响应头必须声明 Content-Type: application/json; charset=utf-8,否则前端可能按 latin-1 解码
  • 若数据要写入文件,推荐显式指定 编码with open("out.json", "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False)

自定义类实例无法直接序列化

定义了 class User: 并创建实例后,直接 json.dumps(user) 会报 TypeError: Object of type User is not JSON serializable,哪怕它只有几个普通属性。

原因在于 json 模块不认识你的类,也不自动调用 __dict__——它只认内置类型和你显式告诉它的规则。

  • 简单对象可用 default=lambda o: o.__dict__,但仅限于纯数据类,且不能有循环引用、不可序列化属性(如文件句柄)
  • 更稳的方式是让类实现 to_dict() 方法,再在 default 中调用:default=lambda o: o.to_dict() if hasattr(o, "to_dict") else None
  • 别依赖 vars()__dict__ 处理带 property、动态属性或私有字段的类,容易漏字段或暴露不该导出的内容

真正难的不是怎么序列化,而是怎么确保反序列化后还能还原语义——比如时间精度丢没丢、浮点误差有没有放大、空值是 null 还是 "null" 字符串。这些边界一旦跨过,debug 成本远高于加几行 default 函数。

text=ZqhQzanResources