Python JSON编码器支持日期时间?
Posted
技术标签:
【中文标题】Python JSON编码器支持日期时间?【英文标题】:Python JSON encoder to support datetime? 【发布时间】:2012-08-20 18:17:24 【问题描述】:有没有什么优雅的方法可以让 Python JSON 编码器支持日期时间?一些第 3 方模块或简单的 hack?
我正在使用 tornado 的数据库包装器从 db 中获取一些行以生成 json。查询结果包含一个常规的 mysql 时间戳列。
很烦人的是 Python 的默认 json 编码器不支持它自己的 datetime 类型,这在各种数据库查询中都很常见。
我不想修改 Python 自己的 json 编码器。有什么好的做法吗?非常感谢!
ps:我通过修改 Python JSON 编码器默认方法发现了一个肮脏的 hack:
变化:
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 源代码。我可以用我自己的项目代码做些什么来实现这一点吗?最好是一步到位。
非常感谢!
【问题讨论】:
见:***.com/questions/455580/… 子类方法的问题,是它对于所有其他json编码的使用都失败了,比如simple django, "dumpdata" 【参考方案1】:json.dumps(thing, default=str)
【讨论】:
如果您认为这符合问题中提到的“easy hack”的定义,请点赞。 我想这应该是正确的答案,不知道这个答案有什么问题? 我觉得很有趣。 5 年后,这仍然是解决这个问题的最简单和 Pythonic 的方法,但这个答案仍然被低估了。 当thing
是Dict
时,这实际上保留了float 和int 值。
你宁愿同意一些常见的格式并在你传递给default
的某个函数中使用strftime
【参考方案2】:
The docs suggest 继承 JSONEncoder 并实现您自己的默认方法。好像你基本上在那里,而且它不是一个“肮脏的黑客”。
默认编码器不处理日期的原因是 JSON 中没有标准的日期表示。 Some people 使用的是 /Date(1198908717056)/
格式,但我个人更喜欢 ISO 格式。
import json
import datetime
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime.datetime, datetime.date, datetime.time)):
return obj.isoformat()
elif isinstance(obj, datetime.timedelta):
return (datetime.datetime.min + obj).time().isoformat()
return super(DateTimeEncoder, self).default(obj)
now = datetime.datetime.now()
encoder = DateTimeEncoder()
encoder.encode("datetime": now, "date": now.date(), "time": now.time())
> "datetime": "2019-07-02T16:17:09.990126", "date": "2019-07-02", "time": "16:17:09.990126"
【讨论】:
您可以将if
和第一个elif
组合在一起,因为您对return obj.isoformat()
执行相同的操作并且isinstance
支持元组:isinstance(obj, (datetime.datetime, datetime.date))
跨度>
这对我有用,我在现有的自定义 JSON 编码器中添加了额外的检查
此timedelta
编码对于超过(或等于)24 小时的值会失败。例如。对于timedelta(hours=24)
,编码值为00:00:00
(0 小时),对于timedelta(hours=25)
,编码值为01:00:00
(1 小时)。
@cod3monk3y 如果您的时间增量超过 24 小时,请使用 total_seconds
方法,或进行手动字符串格式化。 isoformat 对于 stort 来说是一个简单的技巧。【参考方案3】:
我为我的项目制作了自己的课程:
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)
参考资料:
json Documentation Mark Hildreth -- Subclassing JSONEncoder and JSONDecoder Cédric Krier -- trytond.protocols.jsonrpc source code注意:通过将类型作为键和参数、kwargs 作为值的自定义字典传递给编码器的__init__()
并在default()
方法中使用它(或默认字典),可以使其更加灵活。
【讨论】:
迄今为止的最佳答案。当我试图了解如何反序列化 UUID 时,它会有所帮助。太好了! 很高兴您对此感到满意。我自己用过几次。【参考方案4】:json.dumps(r, default=lambda o: o.isoformat() if hasattr(o, 'isoformat') else o)
【讨论】:
虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。【参考方案5】:Tryton 项目为 datetime.datetime
、datetime.date
和 datetime.time
对象(与其他对象)提供了 JSONEncoder 实现。它用于服务器和客户端之间的 JSON RPC 通信。
见http://hg.tryton.org/2.4/trytond/file/ade5432ac476/trytond/protocols/jsonrpc.py#l53
【讨论】:
【参考方案6】:创建自定义解码器/编码器:
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】:将 datetime 类型转换为 unix 时间戳,然后将内容编码为 json。
例如: http://codepad.org/k3qF09Kr
【讨论】:
你的意思是改变 Python 的默认日期时间类型?怎么做?会不会太冒险?它会破坏东西吗? @horacex 不,只需修改来自 tornado 数据库包装器的结果集。【参考方案8】:我建议使用ujson
包或orjson
包。
它们速度更快,并且仍然支持多种复杂类型。
【讨论】:
ujson
对我不起作用(至少不是开箱即用)。我得到了:TypeError: datetime.datetime(2020, 4, 18, 17, 50, 42, 418419, tzinfo=datetime.timezone(datetime.timedelta(seconds=10800))) is not JSON serializable
ujson
本身不会序列化 datetime.datetime
。然而,orjson
成功了。 orjson
还序列化了其他常用类型,如uuid.UUID
。【参考方案9】:
只需创建一个自定义编码器
(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
当然,如果您在编码之前将数据帧放入更大的字典中,这也将起作用,例如
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)
【讨论】:
以上是关于Python JSON编码器支持日期时间?的主要内容,如果未能解决你的问题,请参考以下文章