Python自定义JSONEncoder时json.dumps()正常但json.dump()日期键报错
问题根源
你重写的是encode()方法,这个方法只有json.dumps()会完整调用:dumps()的执行逻辑是直接调用编码器实例的encode()方法拿到完整字符串结果,所以你在encode()里提前把date类型键转成字符串的逻辑可以正常运行。
但json.dump()走的是增量迭代编码逻辑,它不会直接调用你重写的encode()方法处理整个对象,而是直接调用底层的_iterencode生成器逐块遍历数据写入文件,遍历字典时会先校验键的类型,这时候还没走到你写的键转换逻辑,就直接抛出TypeError了。
另外你之前尝试的default=参数无效是正常现象:default只会在序列化值遇到无法识别的类型时触发,完全不会处理字典的键,所以调整default的实现永远解决不了键类型不合法的问题。
正确修改方案
不要重写encode()方法,改为重写编码器内部被迭代编码逻辑调用的iterencode()方法,让它在开始迭代编码前先完成日期键的转换,这样dumps()和dump()都能正常生效:
import json import datetime class JSONSerializer(json.JSONEncoder): @staticmethod def convert_if_date(_date): if isinstance(_date, datetime.date): return _date.strftime('%Y-%m-%d') return _date def date_insensitive_encode(self, obj): if isinstance(obj, dict): return {self.convert_if_date(k): self.date_insensitive_encode(v) for k, v in obj.items()} if isinstance(obj, list): return [self.date_insensitive_encode(item) for item in obj] if isinstance(obj, tuple): return tuple(self.date_insensitive_encode(item) for item in obj) return obj def iterencode(self, obj, _one_shot=False): return super().iterencode(self.date_insensitive_encode(obj), _one_shot=_one_shot) test_dict = {datetime.date(2022, 7, 10): 'OK'} # 测试dumps print(json.dumps(test_dict, cls=JSONSerializer)) # 输出: {"2022-07-10": "OK"} # 测试dump with open('dump.json', 'w') as w: json.dump(test_dict, w, cls=JSONSerializer) # 正常写入无报错
注:上面的转换逻辑补了对嵌套字典、列表、元组的递归处理,如果要序列化的结构存在嵌套的日期键也能正常转换,如果只有一层字典可以删掉递归部分简化逻辑。
轻量变通方案(无需自定义编码器)
如果只是单次写入、不需要复用序列化类,可以直接手动转换完字典的键再传给json方法,代码更简洁:
def convert_dict_date_key(obj): if isinstance(obj, dict): return { k.strftime('%Y-%m-%d') if isinstance(k, datetime.date) else k: convert_dict_date_key(v) for k,v in obj.items() } if isinstance(obj, (list, tuple)): return type(obj)(convert_dict_date_key(i) for i in obj) return obj # 直接传入转换后的对象即可 with open('dump.json', 'w') as w: json.dump(convert_dict_date_key(test_dict), w)
内容的提问来源于stack exchange,提问作者Plaargath
相关产品推荐
相关产品推荐

