尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Python json 序列化实战:datetime、Decimal、自定义对象报 TypeError 的三种解法

Python json 序列化实战:datetime、Decimal、自定义对象报 TypeError 的三种解法 Python json 序列化实战:datetime、Decimal、自定义对象报 TypeError 的三种解法你写了个接口,把查出来的数据json.dumps一下返回,结果崩在这行:TypeError: Object of type datetime is not JSON serializable换成金额字段又变成Object of type Decimal is not JSON serializable,自定义的类更是直接Object of type User is not JSON serializable。这不是偶发 bug,而是 Python 标准库json的硬规则:它只认识一小撮内置类型,别的一律拒绝。这篇讲清楚为什么会报,再给三种能落地的解法,最后说反序列化怎么把这些类型再还原回来。为什么会报 TypeErrorjson模块只能序列化这几种类型:dict、list/tuple、str、int/float、bool、None。就这些。碰到不认识的对象,它不会猜、也不会调str(),而是直接抛TypeError。importjsonfromdatetimeimportdatetimefromdecimalimportDecimal data{name:订单,created:datetime(2026,8,4,9,0,0),amount:Decimal(19.90),}json.dumps(data)# TypeError: Object of type datetime is not JSON serializable原因很合理:datetime该转成什么格式?ISO 字符串还是时间戳?Decimal(19.90)转 float 会丢精度变成19.9。这些只有你自己知道该怎么转,所以标准库把决定权交给你。下面三种解法,本质都是「告诉 json 遇到这些类型该怎么办」。解法一:default 回调(临时、局部最灵活)json.dumps有个default参数,接一个函数。当 json 遇到搞不定的对象时,会把这个对象传给你的函数,拿返回值继续序列化:importjsonfromdatetimeimportdatetime,datefromdecimalimportDecimaldefjson_default(obj):# 只有 json 自己处理不了时才会调到这里ifisinstance(obj,(datetime,date)):returnobj.isoformat()# datetime - 2026-08-04T09:00:00ifisinstance(obj,Decimal):returnstr(obj)# 转成 19.90 字符串,保精度# 兜底:明确抛错,而不是返回 None 悄悄吞掉raiseTypeError(f不支持序列化的类型:{type(obj).__name__})data{created:datetime(2026,8,4,9,0),amount:Decimal(19.90)}print(json.dumps(data,defaultjson_default,ensure_asciiFalse))# {created: 2026-08-04T09:00:00, amount: 19.90}三个要点:default只在标准类型处理不了时才被调用,对 str、int 这些它根本不会触发,所以你的函数只需处理「异类」。Decimal 用str()而不是float()。金额场景转 float 会引入浮点误差(Decimal(0.1)→0.1看着没事,但Decimal(19.99)参与运算就可能变19.990000000000002),用字符串前端能原样拿到再转,精度不丢。兜底raise TypeError,别return None。返回 None 会把没考虑到的类型悄悄变成null,数据错了都不知道;抛错才能第一时间发现遗漏。default的好处是不污染全局、哪里用哪里传,适合一次性场景。解法二:自定义 JSONEncoder(项目里统一复用)如果整个项目到处都要序列化这些类型,每次传default太啰嗦。可以继承json.JSONEncoder,把规则固化成一个类:importjsonfromdatetimeimportdatetime,datefromdecimalimportDecimalclassEnhancedJSONEncoder(json.JSONEncoder):defdefault(self,obj):# 注意:这里方法名固定叫 default,重写它ifisinstance(obj,(datetime,date)):returnobj.isoformat()ifisinstance(obj,Decimal):returnstr(obj)ifisinstance(obj,set):returnlist(obj)# set 也不被支持,顺手处理returnsuper().default(obj)# 交回父类抛标准 TypeErrordata{created:datetime.now(),amount:Decimal(19.90),tags:{a,b}}# 用法一:显式传 clsprint(json.dumps(data,clsEnhancedJSONEncoder,ensure_asciiFalse))# 用法二:封装成项目统一入口,团队都调它defdumps(obj,**kwargs):kwargs.setdefault(ensure_ascii,False)returnjson.dumps(obj,clsEnhancedJSONEncoder,**kwargs)和解法一相比,Encoder 类的优势是可复用、可继承、能封装成团队统一的dumps。注意最后一行super().default(obj)——遇到连你也没处理的类型,交回父类抛标准错误,别自己吞。解法三:让对象自己知道怎么变成 dict前两种解法处理的是「datetime、Decimal 这种你改不了源码的类型」。但对于你自己写的类,更干净的做法是让它自带序列化方法,而不是在 encoder 里堆一堆isinstance:importjsonfromdataclassesimportdataclass,asdictfromdatetimeimportdatetimedataclassclassUser:id:intname:strcreated:datetimedefto_dict(self):dasdict(self)# dataclass 转 dictd[created]self.created.isoformat()returnd uUser(1,张三,datetime(2026,8,4,9,0))# 对象自己给出可序列化的 dict,json 只管处理 dictprint(json.dumps(u.to_dict(),ensure_asciiFalse))# {id: 1, name: 张三, created: 2026-08-04T09:00:00}dataclasses.asdict能把 dataclass 递归转成 dict,连嵌套的 dataclass 也一起转,省去手写。对于普通类,自己写个to_dict返回纯 dict 即可。这种「对象负责把自己表达成基础类型」的思路,比在全局 encoder 里穷举类型更好维护——加字段时改一处,和数据定义在一起。如果你已经在用 Pydantic,直接model.model_dump_json()就全搞定了,datetime、Decimal、嵌套模型都自动处理,项目里能用就别自己造轮子。反序列化:怎么把字符串还原成 datetime序列化是单向的——2026-08-04T09:00:00读回来还是个字符串,json不会自动帮你还原成datetime,因为它无从判断这个字符串到底是不是时间。要还原,用object_hook:importjsonfromdatetimeimportdatetimedefjson_object_hook(d):# json.loads 解析出每个 dict 都会过一遍这个函数forkey,valueind.items():ifkeyin(created,updated)andisinstance(value,str):try:d[key]datetime.fromisoformat(value)exceptValueError:pass# 转不了就保持原样,别炸returnd raw{id: 1, created: 2026-08-04T09:00:00}objjson.loads(raw,object_hookjson_object_hook)print(type(obj[created]))# class datetime.datetimeobject_hook会对解析出的每一个 dict调用一次,你按字段名或值的特征判断要不要转。这里用datetime.fromisoformat(Python 3.7)配对前面的isoformat(),一存一取正好闭环。转不动时别抛异常,保持原值更稳。一个容易忽略的细节:ensure_ascii上面几段都带了ensure_asciiFalse,这不是可选项。json.dumps默认ensure_asciiTrue,会把中文转成\uXXXX:importjsonprint(json.dumps({name:订单}))# {name: 订单}print(json.dumps({name:订单},ensure_asciiFalse))# {name: 订单}两种都是合法 JSON,前端也能正确解析回中文,但日志、调试、直接返回给人看时,订单完全没法读。只要结果里可能有中文,就加ensure_asciiFalse,这也是为什么建议把项目的dumps封装一层统一带上。小结json只认识 dict/list/str/int/float/bool/None,遇到datetime、Decimal、自定义类必抛TypeError,这是设计如此,不是 bug。三种解法按场景选:一次性用default回调;项目复用继承JSONEncoder;自己的类让它带to_dict/用 dataclass 或 Pydantic。Decimal 转str不转float,金额精度不能丢;encoder 里遇到没处理的类型要raise/交回父类,别悄悄返回 None。反序列化不会自动还原类型,要还原 datetime 用object_hookfromisoformat,和isoformat()配对闭环。一句话记忆点:json 报「not JSON serializable」不是让你查 bug,是在问你「这个类型该转成什么」——回答它(default/encoder/to_dict)就行。
返回列表