如何解决Python Flask中Decimal类型无法序列化JSON_自定义JSONEncoder
必须重写JSONEncoder,因jsonify依赖json.dumps默认不支持Decimal;需继承flask.json.JSONEncoder并重写default方法,对Decimal转float后交由父类处理,再通过app.json_encoder全局注册。
直接结论:必须重写 JSONEncoder,不能靠 int() 或 float() 强转字段再传给 jsonify —— 那只是掩盖问题,且在精度丢失、负零、NaN 等边界场景会出错。
为什么 jsonify 会报 Object of type 'Decimal' is not JSON serializable
Flask 的 jsonify 内部调用的是 json.dumps(),而 Python 标准库的 json 模块默认只认 int、float、str 等原生类型。decimal.Decimal 是独立实现的高精度类型,不在默认映射表里。一旦数据库查询返回含 Decimal 的元组或字典(比如 SELECT SUM(price)),jsonify 就会直接抛错。
常见错误现象:
- 返回 HTTP 500,日志里只有
TypeError: Object of type 'Decimal' is not JSON serializable - 错误堆栈指向
flask/json/__init__.py中的dumps调用 - 仅在连接 MySQL/PostgreSQL 并使用原生驱动(如
pymysql、psycopg2)时高频出现,SQLite 有时不暴露该问题(因部分驱动自动转float)
如何正确继承并注册 JSONEncoder(Flask 2.0+ 推荐方式)
从 Flask 2.0 开始,simplejson 已被移除,必须通过子类化 flask.json.JSONEncoder 并赋值给 app.json_encoder(旧版用 app.json_encoder,新版推荐 app.json.provider_class,但兼容写法仍有效)。
实操建议:
- 定义一个新类,继承
flask.json.JSONEncoder,重写default方法 - 在
default中只处理decimal.Decimal,其余一律交还父类(super().default(o)),避免覆盖对datetime等其他类型的默认支持 - 确保
return float(o)而非str(o)—— JSON number 类型需要数值,不是字符串;str(o)会导致前端解析为字符串,破坏数值比较和计算逻辑 - 注册时用
app.json_encoder = YourEncoder,不要漏掉这一步
示例代码:
Find JSON Path Online
Easily find JSON paths within JSON objects using our intuitive Json Path Finder
from flask import Flask
from flask.json import JSONEncoder
import decimal
class DecimalJSONEncoder(JSONEncoder):
def default(self, o):
if isinstance(o, decimal.Decimal):
return float(o)
return super().default(o)
app = Flask(__name__)
app.json_encoder = DecimalJSONEncoder
为什么不用 json.dumps(..., cls=...) 手动序列化
你当然可以绕过 jsonify,自己调用 json.dumps(data, cls=DecimalEncoder) 再包装成 Response,但这会带来三个实际问题:
- 丢失
jsonify自动设置的Content-Type: application/json和状态码处理逻辑 - 无法复用 Flask 对嵌套对象、
None、enum等的默认编码行为,容易引发新错误 - 团队协作中,每个接口都得显式写
cls=...,维护成本陡增;而全局注册一次JSONEncoder后,所有jsonify调用自动生效
更关键的是:如果你用了 Flask-RESTful、Flask-JWT-Extended 等扩展,它们内部也依赖 jsonify,手动 dumps 无法覆盖这些路径。
精度与兼容性要注意的细节
float(o) 看似简单,但有隐含风险:
-
Decimal('0.1')→float后是0.10000000000000000555...,前端 JS 解析时可能显示为0.10000000000000000555(尤其在金融类金额展示中不可接受) - 若需保留小数位(如金额统一两位),应在业务层做
o.quantize(decimal.Decimal('0.01'))后再转float,而不是在JSONEncoder里硬编码格式化逻辑 - 某些老版本 Flask(json_encoder 属性名是
json_encoder,新版(2.2+)推荐用app.json.provider_class,但直接设json_encoder在当前主流版本(2.0–2.3)仍完全可用
真正容易被忽略的点:这个修复只解决 Decimal,如果数据里同时混有 datetime、bytes、自定义 model 实例,default 方法必须逐个判断并处理,否则仍会崩 —— JSONEncoder.default 是唯一出口,别指望它“自动兜底”。