有没有优雅的方法让Python JSON编码器支持日期时间?一些第三方模块或简单的黑客?
我正在使用tornado的数据库包装器从db中获取一些行来生成json。查询结果包括常规的MySQL时间戳列。
Python的默认json编码器不支持自己的日期时间类型,这非常令人讨厌,这在所有类型的数据库查询中都很常见。
我不想修改Python自己的json编码器。任何好的做法?非常感谢!
ps:我通过修改Python JSON编码器默认方法找到了一个肮脏的黑客:
变化:
def default(self, o):
raise TypeError(repr(o) + " is not JSON serializable")
要:
def default(self, o):
from datetime import date
from datetime import datetime
if isinstance(o, datetime):
return o.isoformat()
elif isinstance(o, date):
return o.isoformat()
else:
raise TypeError(repr(o) + " is not JSON serializable")
嗯,这将是一个临时解决方案,仅适用于开发环境。
但是对于长期解决方案或生产环境,这非常难看,每次部署到新服务器时我都必须进行修改。
有更好的方法吗?我不想修改Python代码本身,也不想修改Tornado源代码。我能用自己的项目代码做些什么来实现这一目标吗?最好是一步到位。
非常感谢!
答案 0 :(得分:19)
The docs suggest继承JSONEncoder并实现自己的默认方法。好像你基本上就在那里,而且这不是一个“肮脏的黑客”。
默认编码器未处理日期的原因是JSON中没有日期的标准表示。 Some people使用格式/Date(1198908717056)/
,但我个人更喜欢ISO格式。
import datetime
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.isoformat()
elif isinstance(obj, datetime.date):
return obj.isoformat()
elif isinstance(obj, datetime.timedelta):
return (datetime.datetime.min + obj).time().isoformat()
else:
return super(DateTimeEncoder, self).default(obj)
DateTimeEncoder().encode(object)
答案 1 :(得分:17)
json.dumps(thing, default=str)
答案 2 :(得分:8)
我为自己的项目制作了自己的课程:
import datetime
import decimal
import json
import sys
class EnhancedJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
ARGS = ('year', 'month', 'day', 'hour', 'minute',
'second', 'microsecond')
return {'__type__': 'datetime.datetime',
'args': [getattr(obj, a) for a in ARGS]}
elif isinstance(obj, datetime.date):
ARGS = ('year', 'month', 'day')
return {'__type__': 'datetime.date',
'args': [getattr(obj, a) for a in ARGS]}
elif isinstance(obj, datetime.time):
ARGS = ('hour', 'minute', 'second', 'microsecond')
return {'__type__': 'datetime.time',
'args': [getattr(obj, a) for a in ARGS]}
elif isinstance(obj, datetime.timedelta):
ARGS = ('days', 'seconds', 'microseconds')
return {'__type__': 'datetime.timedelta',
'args': [getattr(obj, a) for a in ARGS]}
elif isinstance(obj, decimal.Decimal):
return {'__type__': 'decimal.Decimal',
'args': [str(obj),]}
else:
return super().default(obj)
class EnhancedJSONDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
super().__init__(*args, object_hook=self.object_hook,
**kwargs)
def object_hook(self, d):
if '__type__' not in d:
return d
o = sys.modules[__name__]
for e in d['__type__'].split('.'):
o = getattr(o, e)
args, kwargs = d.get('args', ()), d.get('kwargs', {})
return o(*args, **kwargs)
if __name__ == '__main__':
j1 = json.dumps({'now': datetime.datetime.now(),
'val': decimal.Decimal('9.3456789098765434987654567')},
cls=EnhancedJSONEncoder)
print(j1)
o1 = json.loads(j1, cls=EnhancedJSONDecoder)
print(o1)
结果:
{"val": {"args": ["9.3456789098765434987654567"], "__type__": "decimal.Decimal"}, "now": {"args": [2014, 4, 29, 11, 44, 57, 971600], "__type__": "datetime.datetime"}}
{'val': Decimal('9.3456789098765434987654567'), 'now': datetime.datetime(2014, 4, 29, 11, 44, 57, 971600)}
参考文献:
注意:通过将类型为键和args,kwargs作为值的自定义字典传递给编码器的__init__()
并在default()
中使用它(或默认字典),可以使其更加灵活方法
答案 3 :(得分:3)
json.dumps(r, default=lambda o: o.isoformat() if hasattr(o, 'isoformat') else o)
答案 4 :(得分:1)
Tryton项目为datetime.datetime
,datetime.date
和datetime.time
个对象(以及其他对象)提供了JSONEncoder实现。它用于服务器和客户端之间的JSON RPC通信。
请参阅http://hg.tryton.org/2.4/trytond/file/ade5432ac476/trytond/protocols/jsonrpc.py#l53
答案 5 :(得分:0)
将datetime类型转换为unix时间戳,然后将内容编码为json。
e.g。 : http://codepad.org/k3qF09Kr
答案 6 :(得分:0)
创建自定义解码器/编码器:
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return http_date(obj)
if isinstance(obj, uuid.UUID):
return str(obj)
return json.JSONEncoder.default(self, obj)
class CustomJSONDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs)
def object_hook(self, source):
for k, v in source.items():
if isinstance(v, str):
try:
source[k] = datetime.datetime.strptime(str(v), '%a, %d %b %Y %H:%M:%S %Z')
except:
pass
return source
答案 7 :(得分:-1)
只需创建一个自定义编码器
(Cole的答案的一个小但重要的补充是处理pd.NaT(或空/空时间戳值),因为如果没有添加,你将获得NaT /缺少时间戳数据的非常奇怪的时间戳转换)
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if pd.isnull(obj):
return None
elif isinstance(obj, datetime):
return obj.isoformat()
elif isinstance(obj, date):
return obj.isoformat()
elif isinstance(obj, timedelta):
return (datetime.min + obj).time().isoformat()
else:
return super(CustomEncoder, self).default(obj)
然后用它来编码数据帧:
df_as_dict = df.to_dict(outtype = 'records') # transform to dict
df_as_json = CustomEncoder().encode(df_as_dict) #transform to json
由于编码器对数据进行了标准化,因此常规解码器可以很好地将其转换回数据帧:
result_as_dict = json.JSONDecoder().decode(df_as_json) # decode back to dict
result_df = pd.DataFrame(result) # transform dict back to dataframe
当然,如果在编码之前将数据帧放入更大的dict中,这也会有效,例如
input_dict = {'key_1':val_1,'key_2':val_2,...,'df_as_dict':df_as_dict}
input_json = CustomEncoder().encode(input_dict)
input_json_back_as_dict = json.JSONDecoder().decode(input_json)
input_df_back_as_dict = input_json_back_as_dict['df_as_dict']
input_df_back_as_df = pd.DataFrame(input_df_back_as_dict)
答案 8 :(得分:-1)