json.dumps() 如何优雅处理 datetime/decimal/自定义类对象

json.dumps() 默认不支持 datetime、Decimal 和自定义类,需通过 default 参数或自定义 JSONEncoder 实现序列化:datetime/date 转 ISO 字符串,Decimal 推荐转 str 保精度,自定义类优先用 to_dict() 方法。

直接用 json.dumps() 处理 datetimeDecimal 或自定义类对象会报错,因为 JSON 标准只支持 strintfloatboolNonelistdict 这几种类型。解决的关键是提供一个能识别并转换这些“不兼容类型”的 default 回调函数。

用 default 参数统一转换特殊类型

这是最常用也最灵活的方式。你写一个函数,判断对象类型,返回 JSON 可序列化的等价表示:

  • datetime.date / datetime.datetime → 转成 ISO 格式字符串(如 "2025-05-20""2025-05-20T14:30:00.123"
  • decimal.Decimal → 转成 float(注意精度损失)或 str(推荐,保留精度)
  • 自定义类 → 返回其 __dict__,或显式定义字段字典(更可控)

示例:

import json
from datetime import datetime, date
from decimal import Decimal

def json_serializer(obj): if isinstance(obj, (datetime, date)): return obj.isoformat() if isinstance(obj, Decimal): return str(obj) # 避免 float 精度问题 if hasattr(obj, 'dict'): return obj.dict raise TypeError(f"Object of type {type(obj)} is not JSON serializable")

data = { "time": datetime.now(), "price": Decimal("99.99"), "user": type('User', (), {"name": "Alice", "age": 30})() }

json.dumps(data, default=json_serializer, ensure_ascii=False)

为自定义类实现 to_dict() 方法更清晰

比起依赖 __dict__(可能包含私有属性、方法、循环引用),在类里明确定义 to_dict() 更安全、可维护性更高:

class Product:
    def __init__(self, name, price, created_at):
        self.name = name
        self.price = price
        self.created_at = created_at
def to_dict(self):
    return {
        "name": self.name,
        "price": str(self.price),
        "created_at": self.created_at.isoformat() if self.created_at else None
    }

在 serializer 中调用

def json_serializer(obj): if hasattr(obj, 'to_dict') and callable(getattr(obj, 'to_dict')): return obj.to_dict()

其他类型处理...

用 cls 参数继承 JS

ONEncoder 更适合项目级复用

如果多个地方都需要相同规则,继承 json.JSONEncoder 并重写 default 方法,代码更结构化,也方便统一配置(比如全局控制日期格式):

class CustomJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.strftime("%Y-%m-%d %H:%M:%S")
        if isinstance(obj, date):
            return obj.isoformat()
        if isinstance(obj, Decimal):
            return float(obj) if obj.as_tuple().exponent >= 0 else str(obj)
        if hasattr(obj, 'to_dict'):
            return obj.to_dict()
        return super().default(obj)

json.dumps(data, cls=CustomJSONEncoder, ensure_ascii=False)

小提醒:避免踩坑

别用 str(obj) 一锅端 —— 对于 datetime 是 OK 的,但对 Decimal('1.00') 得到 "1.00" 还行,而对自定义类可能输出 "<__main__.user object at>",毫无意义。
慎用 float(Decimal) —— 小数位数多时会丢失精度,金融场景务必用 str()
注意嵌套对象 —— 如果自定义类属性里还有 datetime 或 Decimal,确保 to_dict()default 递归处理它们。