json.dumps() 默认不支持自定义类序列化,需通过 default 参数传入转换函数返回 json 原生类型;反序列化需用 object_hook 还原对象;推荐用 isinstance 判断类型并手动提取字段,避免 dict 或递归调用引发错误。

为什么 json.dumps() 直接序列化自定义类会报 TypeError: Object of type XXX is not JSON serializable
因为 json 模块默认只认 dict、list、str、int、float、bool 和 None 这几种类型。遇到自定义类实例(比如 User(name="alice", age=30)),它根本不知道怎么转成 JSON 字符串,直接抛错。
解决思路不是改 json 模块,而是告诉它:「这个对象该怎么展开」——靠 default 参数传一个可调用对象。
-
default函数接收任意无法序列化的对象,必须返回一个 JSON 可序列化的值(如dict),否则照样报错 - 别在
default里做复杂逻辑或递归调用json.dumps(),容易栈溢出 - 如果对象有循环引用(比如
A.b = B且B.a = A),default不处理的话仍会崩,得额外加检测
如何用 default 参数把自定义对象转成字典
最常用也最可控的方式:写一个函数,判断对象类型,手动提取字段。
def serialize_obj(obj):
if isinstance(obj, User):
return {"name": obj.name, "age": obj.age, "is_active": obj.is_active}
elif isinstance(obj, datetime.datetime):
return obj.isoformat()
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
json.dumps(data, default=serialize_obj)
注意两点:
- 不要用
obj.__dict__直接返回(可能包含私有属性、方法、不可序列化字段) - 多个类共用一个
default函数时,用isinstance显式判断比hasattr更安全——避免误匹配有同名属性的无关类 - 如果类字段很多,可以配合
dataclasses.asdict()或pydantic.BaseModel.model_dump(),但前提是类本身支持(比如是@dataclass或继承自BaseModel)
反序列化时怎么还原成自定义对象
json.loads() 只负责变回 Python 原生结构,不会自动构造类实例。还原得靠 object_hook。
它会在每个 JSON 对象(即每个 dict)解析完后被调用一次,传入该 dict,你可以决定返回啥:
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
def build_user(d):
if "name" in d and "age" in d and "is_active" in d:
return User(**d)
return d
json.loads(json_str, object_hook=build_user)
关键细节:
-
object_hook是逐层调用的,嵌套对象里的dict也会进这个函数,所以要加判断条件,避免把普通字典错当成User - 如果 JSON 里混着多种自定义类型,
object_hook里就得按字段组合或加标识字段(比如{"_type": "User", "name": ...})来区分 - 别在
object_hook里做耗时操作(如查数据库),它可能被调用几百次
用 JSONEncoder / JSONDecoder 子类是否更优
当逻辑稳定、复用频繁时,子类确实更清晰,尤其适合封装到工具模块中。
例如:
class UserEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, User):
return {"name": obj.name, "age": obj.age}
return super().default(obj)
json.dumps(data, cls=UserEncoder)
但要注意:
- 子类不能解决反序列化问题,
cls参数只影响编码;解码还得另配object_hook或自定义JSONDecoder - 如果项目里同时要处理
User、Order、Address等多种类型,硬编码在单个Encoder里会让类越来越重,不如用组合式default函数 + 注册表 -
JSONEncoder.default()返回值仍需是 JSON 原生类型,不能返回另一个自定义对象,否则递归失败
真正麻烦的从来不是“怎么写”,而是字段一致性——比如序列化用了 snake_case,反序列化却期待 camelCase,或者漏了某个可选字段的默认值处理。这些细节不提前对齐,后期调试成本远高于写几行 default 函数。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










