问题描述
尝试序列化包含日期时间对象的字典作为json的键。建议使用json.dump
参数default
或cls
来解决其他问题,但是似乎都没有调用这两种方法。请参阅下面的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
值上调用,从不使用键。密钥必须是str
,int
,float
,bool
或None
之一。
要将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