json.dump不调用default或cls

问题描述

尝试序列化包含日期时间对象的字典作为json的键。建议使用json.dump参数defaultcls解决其他问题,但是似乎都没有调用这两种方法。请参阅下面的MWE。我想念什么?

使用Default

from datetime import datetime
import json

def default(obj):
    print("Default Called")
    if isinstance(obj,(datetime,date)):
        return obj.isoformat()

test = {datetime(1970,1,8,0): 10}

with open("output.json","w") as fo:
    json.dump(test,fo,default=default)
Traceback (most recent call last):
  File "test.py",line 21,in <module>
    json.dump(test,default=default)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py",line 179,in dump
    for chunk in iterable:
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py",line 431,in _iterencode
    yield from _iterencode_dict(o,_current_indent_level)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py",line 376,in _iterencode_dict
    raise TypeError(f'keys must be str,int,float,bool or None,'
TypeError: keys must be str,not datetime

使用cls

from datetime import datetime
import json

class DateEncoder(json.JSONEncoder):
    def default(self,obj):
        print("Default called")
        if isinstance(obj,date)):
            return obj.isoformat()

        # Let the base class default method raise the TypeError
        return json.JSONEncoder.default(self,obj)

test = {datetime(1970,cls=DateEncoder)
Traceback (most recent call last):
  File "test.py",line 16,cls=DateEncoder)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py",not datetime

解决方法

您可以尝试以下方法:

class DatesToStrings(json.JSONEncoder):
    def _encode(self,obj):
        if isinstance(obj,dict):
            def transform_date(o):
                return self._encode(o.isoformat() if isinstance(o,datetime) else o)
            return {transform_date(k): transform_date(v) for k,v in obj.items()}
        else:
            return obj

    def encode(self,obj):
        return super(DatesToStrings,self).encode(self._encode(obj))
with open("output.json","w") as fo:
    json.dump( json.dumps( test,cls=DatesToStrings),fo,cls=DatesToStrings)

,

TLDR:仅为.default的值调用dict,而不为键的调用。

详细阐述Abhishek Kumars的答案;在查看json.JSONEncoder源之后,.default仅在不可序列化的dict值上调用,从不使用键。密钥必须是strintfloatboolNone之一。

要将datetime对象用作键,必须重写.iterencode以使用递归预处理器方法将datetime转换为str

test = {
        "key_1": "Value_1","key_2": 10,"key_3": ["list_" + str(i) for i in range(5)],"key_4": {"nestkey_" + str(i) : "nestvalue_" + str(i) for i in range(5) },"key_5": datetime.datetime(1970,1,8,0),datetime.datetime(1970,0): "datetime_key"
}

class DateTimeEncoder(json.JSONEncoder):

    def _preprocess_date(self,(datetime.date,datetime.datetime,datetime.timedelta)):
            return str(obj)
        elif isinstance(obj,dict):
            return {self._preprocess_date(k): self._preprocess_date(v) for k,v in obj.items()}
        elif isinstance(obj,list):
            return [self._preprocess_date(i) for i in obj]
        return obj

    def default(self,datetime.timedelta)):
            return str(obj)
        return super().default(obj)

    def iterencode(self,obj):
        return super().iterencode(self._preprocess_date(obj))

with open('output.json','w') as fo:
    json.dump(test,cls=DateTimeEncoder)

对于较大的词典来说,这显然是昂贵的操作,因此应谨慎行事。另外,最好将json.JSONEncoder更新为在键和值上使用默认值-问题在https://bugs.python.org/issue41569

中创建