我怎样才能克服“datetime.datetime 不是 JSON 可序列化”的问题?
- 2024-12-05 08:38:00
- admin 原创
- 138
问题描述:
我有一个基本的字典如下:
sample = {}
sample['title'] = "String"
sample['somedate'] = somedatetimehere
当我尝试这样做时,jsonify(sample)
我得到:
TypeError:datetime.datetime(2012, 8, 8, 21, 46, 24, 862000) 不是 JSON 可序列化的
我该怎么做才能使我的字典样本能够克服上述错误?
注意:虽然可能不相关,但字典是从检索记录生成的,mongodb
当我打印出时str(sample['somedate'])
,输出是2012-08-08 21:46:24.862000
。
解决方案 1:
我的快速而肮脏的 JSON 转储,包含日期和所有内容:
json.dumps(my_dictionary, indent=4, sort_keys=True, default=str)
default
是一个应用于不可序列化对象的函数。在本例中是
str
,所以它只是将不知道的所有内容转换为字符串。这对于序列化来说很棒,但在反序列化时就不是那么好了(因此是“快速而肮脏的”),因为任何东西都可能在没有警告的情况下被字符串化,例如函数或 numpy 数组。
解决方案 2:
在其他答案的基础上,基于特定序列化器的简单解决方案,仅将对象转换datetime.datetime
为datetime.date
字符串。
from datetime import date, datetime
def json_serial(obj):
"""JSON serializer for objects not serializable by default json code"""
if isinstance(obj, (datetime, date)):
return obj.isoformat()
raise TypeError ("Type %s not serializable" % type(obj))
如您所见,代码只是检查对象是否属于类datetime.datetime
或datetime.date
,然后使用.isoformat()
它根据 ISO 8601 格式 YYYY-MM-DDTHH:MM:SS(JavaScript 可以轻松解码)生成它的序列化版本。如果寻求更复杂的序列化表示,可以使用其他代码代替 str()(有关示例,请参阅此问题的其他答案)。代码以引发异常结束,以处理使用不可序列化类型调用的情况。
此 json_serial 函数可按如下方式使用:
from datetime import datetime
from json import dumps
print dumps(datetime.now(), default=json_serial)
有关 json.dumps 的默认参数如何工作的详细信息,请参阅json 模块文档的基本用法部分。
解决方案 3:
2018 年更新
原始答案适应了 MongoDB“日期”字段的表示方式:
{"$date": 1506816000000}
如果您想要一个用于序列化为datetime
json 的通用 Python 解决方案,请查看@jjmontes 的答案,以获得不需要任何依赖项的快速解决方案。
由于您正在使用 mongoengine(根据评论)并且 pymongo 是一个依赖项,因此 pymongo 具有内置实用程序来帮助进行 json 序列化:
http://api.mongodb.org/python/1.10.1/api/bson/json_util.html
使用示例(序列化):
from bson import json_util
import json
json.dumps(anObject, default=json_util.default)
使用示例(反序列化):
json.loads(aJsonString, object_hook=json_util.object_hook)
Django
Django 提供了一个本机DjangoJSONEncoder
序列化器来正确处理此类问题。
请参阅https://docs.djangoproject.com/en/dev/topics/serialization/#djangojsonencoder
from django.core.serializers.json import DjangoJSONEncoder
return json.dumps(
item,
sort_keys=True,
indent=1,
cls=DjangoJSONEncoder
)
我注意到使用这样的DjangoJSONEncoder
自定义方法和使用default
下面的自定义方法之间存在一个区别:
import datetime
import json
def default(o):
if isinstance(o, (datetime.date, datetime.datetime)):
return o.isoformat()
return json.dumps(
item,
sort_keys=True,
indent=1,
default=default
)
Django 是否剥离了一些数据:
"last_login": "2018-08-03T10:51:42.990", # DjangoJSONEncoder
"last_login": "2018-08-03T10:51:42.990239", # default
所以,在某些情况下您可能需要对此小心谨慎。
解决方案 4:
我刚刚遇到这个问题,我的解决方案是子类化json.JSONEncoder
:
from datetime import datetime
import json
class DateTimeEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, datetime):
return o.isoformat()
return json.JSONEncoder.default(self, o)
在您的通话中执行以下操作:json.dumps(yourobj, cls=DateTimeEncoder)
我.isoformat()
从上面的一个答案中得到了。
解决方案 5:
将日期转换为字符串
sample['somedate'] = str( datetime.utcnow() )
解决方案 6:
对于其他不需要或不想使用 pymongo 库的人,您可以使用这个小代码片段轻松实现日期时间 JSON 转换:
def default(obj):
"""Default JSON serializer."""
import calendar, datetime
if isinstance(obj, datetime.datetime):
if obj.utcoffset() is not None:
obj = obj - obj.utcoffset()
millis = int(
calendar.timegm(obj.timetuple()) * 1000 +
obj.microsecond / 1000
)
return millis
raise TypeError('Not sure how to serialize %s' % (obj,))
然后像这样使用它:
import datetime, json
print json.dumps(datetime.datetime.now(), default=default)
输出:
'1365091796124'
解决方案 7:
以下是我的解决方案:
import json
class DatetimeEncoder(json.JSONEncoder):
def default(self, obj):
try:
return super().default(obj)
except TypeError:
return str(obj)
然后你可以像这样使用它:
json.dumps(dictionary, cls=DatetimeEncoder)
解决方案 8:
如果您使用的是 Python 3.7,那么最好的解决方案是使用datetime.isoformat()
和datetime.fromisoformat()
;它们适用于简单对象和感知datetime
对象:
#!/usr/bin/env python3.7
from datetime import datetime
from datetime import timezone
from datetime import timedelta
import json
def default(obj):
if isinstance(obj, datetime):
return { '_isoformat': obj.isoformat() }
raise TypeError('...')
def object_hook(obj):
_isoformat = obj.get('_isoformat')
if _isoformat is not None:
return datetime.fromisoformat(_isoformat)
return obj
if __name__ == '__main__':
#d = { 'now': datetime(2000, 1, 1) }
d = { 'now': datetime(2000, 1, 1, tzinfo=timezone(timedelta(hours=-8))) }
s = json.dumps(d, default=default)
print(s)
print(d == json.loads(s, object_hook=object_hook))
输出:
{"now": {"_isoformat": "2000-01-01T00:00:00-08:00"}}
True
如果您使用的是 Python 3.6 或更低版本,并且您只关心时间值(而不是时区),那么您可以使用datetime.timestamp()
anddatetime.fromtimestamp()
代替;
如果你使用的是 Python 3.6 或更低版本,并且你确实关心时区,那么你可以通过获取它datetime.tzinfo
,但你必须自己序列化这个字段;最简单的方法是_tzinfo
在序列化对象中添加另一个字段;
最后,要注意所有这些例子中的精确性;
解决方案 9:
您应该将该.strftime()
方法应用于datetime.datetime
对象以使其成为string
。
以下是一个例子:
from datetime import datetime
time_dict = {'time': datetime.now().strftime('%Y-%m-%dT%H:%M:%S')}
sample_dict = {'a': 1, 'b': 2}
sample_dict.update(time_dict)
sample_dict
输出:
Out[0]: {'a': 1, 'b': 2, 'time': '2017-10-31T15:16:30'}
[更新]:
在 Python 3.6 或更高版本中,您可以简单地使用该.isoformat()
方法:
from datetime import datetime
datetime.now().isoformat()
解决方案 10:
json.dumps 方法可以接受一个名为default 的可选参数,该参数应为一个函数。每次 JSON 尝试转换它不知道如何转换的值时,它都会调用我们传递给它的函数。该函数将接收相关对象,并应返回该对象的 JSON 表示形式。
def myconverter(o):
if isinstance(o, datetime.datetime):
return o.__str__()
print(json.dumps(d, default = myconverter))
解决方案 11:
我有一个有类似问题的应用程序;我的方法是将日期时间值 JSON 化为 6 项列表(年、月、日、时、分、秒);你可以将微秒作为 7 项列表,但我不需要:
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
encoded_object = list(obj.timetuple())[0:6]
else:
encoded_object =json.JSONEncoder.default(self, obj)
return encoded_object
sample = {}
sample['title'] = "String"
sample['somedate'] = datetime.datetime.now()
print sample
print json.dumps(sample, cls=DateTimeEncoder)
生成:
{'somedate': datetime.datetime(2013, 8, 1, 16, 22, 45, 890000), 'title': 'String'}
{"somedate": [2013, 8, 1, 16, 22, 45], "title": "String"}
解决方案 12:
我的解决方案(我认为不太冗长):
def default(o):
if type(o) is datetime.date or type(o) is datetime.datetime:
return o.isoformat()
def jsondumps(o):
return json.dumps(o, default=default)
然后使用jsondumps
而不是json.dumps
。它将打印:
>>> jsondumps({'today': datetime.date.today()})
'{"today": "2013-07-30"}'
如果您愿意,稍后您可以通过简单修改default
方法添加其他特殊情况。例如:
def default(o):
if type(o) is datetime.date or type(o) is datetime.datetime:
return o.isoformat()
if type(o) is decimal.Decimal:
return float(o)
解决方案 13:
这个问题一次又一次地重复出现——一个简单的方法是修补json模块,以便序列化支持datetime。
import json
import datetime
json.JSONEncoder.default = lambda self,obj: (obj.isoformat() if isinstance(obj, datetime.datetime) else None)
然后像往常一样使用 JSON 序列化,这次将 datetime 序列化为isoformat。
json.dumps({'created':datetime.datetime.now()})
结果为:‘{“created”:“2015-08-26T14:21:31.853855”}’
查看更多详细信息和一些注意事项:
Stack Overflow:Python 和 JavaScript 之间的 JSON 日期时间
解决方案 14:
这是一个解决“日期时间不是 JSON 可序列化”问题的简单解决方案。
enco = lambda obj: (
obj.isoformat()
if isinstance(obj, datetime.datetime)
or isinstance(obj, datetime.date)
else None
)
json.dumps({'date': datetime.datetime.now()}, default=enco)
输出:-> {“date”:“2015-12-16T04:48:20.024609”}
解决方案 15:
您必须提供一个带有cls
参数的自定义编码器类json.dumps
。引用文档中的一段话:
>>> import json
>>> class ComplexEncoder(json.JSONEncoder):
... def default(self, obj):
... if isinstance(obj, complex):
... return [obj.real, obj.imag]
... return json.JSONEncoder.default(self, obj)
...
>>> dumps(2 + 1j, cls=ComplexEncoder)
'[2.0, 1.0]'
>>> ComplexEncoder().encode(2 + 1j)
'[2.0, 1.0]'
>>> list(ComplexEncoder().iterencode(2 + 1j))
['[', '2.0', ', ', '1.0', ']']
这使用复数作为示例,但您也可以轻松创建一个类来编码日期(但我认为 JSON 对日期有点模糊)。
解决方案 16:
其实很简单。如果您需要经常序列化日期,那么就将它们作为字符串使用。如果需要,您可以轻松地将它们转换回日期时间对象。
如果您需要主要以日期时间对象的形式工作,那么请在序列化之前将它们转换为字符串。
import json, datetime
date = str(datetime.datetime.now())
print(json.dumps(date))
"2018-12-01 15:44:34.409085"
print(type(date))
<class 'str'>
datetime_obj = datetime.datetime.strptime(date, '%Y-%m-%d %H:%M:%S.%f')
print(datetime_obj)
2018-12-01 15:44:34.409085
print(type(datetime_obj))
<class 'datetime.datetime'>
如你所见,两种情况下的输出是相同的。只有类型不同。
解决方案 17:
我通常使用orjson
。不仅因为它的性能出色,还因为它对以下内容的出色支持(符合 RFC-3339)datetime
:
import orjson # via pip3 install orjson
from datetime import datetime
data = {"created_at": datetime(2022, 3, 1)}
orjson.dumps(data) # returns b'{"created_at":"2022-03-01T00:00:00"}'
如果您想使用datetime.datetime
没有 tzinfo 的对象作为 UTC,您可以添加相关选项:
orjson.dumps(data, option=orjson.OPT_NAIVE_UTC) # returns b'{"created_at":"2022-03-01T00:00:00+00:00"}'
解决方案 18:
最简单的方法是将 dict 中 datetime 格式的部分更改为isoformat 。该值实际上是json可以接受的isoformat字符串。
v_dict = version.dict()
v_dict['created_at'] = v_dict['created_at'].isoformat()
解决方案 19:
尝试用一个例子来解析它:
#!/usr/bin/env python
import datetime
import json
import dateutil.parser # pip install python-dateutil
class JSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
return obj.isoformat()
return super(JSONEncoder, self).default(obj)
def test():
dts = [
datetime.datetime.now(),
datetime.datetime.now(datetime.timezone(-datetime.timedelta(hours=4))),
datetime.datetime.utcnow(),
datetime.datetime.now(datetime.timezone.utc),
]
for dt in dts:
dt_isoformat = json.loads(json.dumps(dt, cls=JSONEncoder))
dt_parsed = dateutil.parser.parse(dt_isoformat)
assert dt == dt_parsed
print(f'{dt}, {dt_isoformat}, {dt_parsed}')
# 2018-07-22 02:22:42.910637, 2018-07-22T02:22:42.910637, 2018-07-22 02:22:42.910637
# 2018-07-22 02:22:42.910643-04:00, 2018-07-22T02:22:42.910643-04:00, 2018-07-22 02:22:42.910643-04:00
# 2018-07-22 06:22:42.910645, 2018-07-22T06:22:42.910645, 2018-07-22 06:22:42.910645
# 2018-07-22 06:22:42.910646+00:00, 2018-07-22T06:22:42.910646+00:00, 2018-07-22 06:22:42.910646+00:00
if __name__ == '__main__':
test()
解决方案 20:
我在调试期间经常使用 IntelliJEvaluate Expression
工具来复制某些对象以进行分析。这种方法的问题是,如果您尝试转储的字典中有日期,它就会失效,另一个问题是您无法使用该工具定义转换器,因此我想出了这个一行代码:
json.dumps(dict_to_dump, default=lambda o: o.__str__() if isinstance(o, datetime) else None)
解决方案 21:
如果您正在使用Django模型,您可以直接将其传递encoder=DjangoJSONEncoder
给字段构造函数。它会非常有效。
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.utils.timezone import now
class Activity(models.Model):
diff = models.JSONField(null=True, blank=True, encoder=DjangoJSONEncoder)
diff = {
"a": 1,
"b": "BB",
"c": now()
}
Activity.objects.create(diff=diff)
解决方案 22:
通常,有几种序列化日期时间的方法,例如:
ISO 8601字符串,简短且可以包含时区信息,例如jgbarah 的答案
时间戳(时区数据丢失),例如JayTaylor 的答案
属性字典(包括时区)。
如果您对最后一种方式满意,json_tricks包可以处理日期、时间和日期时间(包括时区)。
from datetime import datetime
from json_tricks import dumps
foo = {'title': 'String', 'datetime': datetime(2012, 8, 8, 21, 46, 24, 862000)}
dumps(foo)
其结果为:
{"title": "String", "datetime": {"__datetime__": null, "year": 2012, "month": 8, "day": 8, "hour": 21, "minute": 46, "second": 24, "microsecond": 862000}}
所以你需要做的就是
`pip install json_tricks`
然后从 而json_tricks
不是导入json
。
不将其存储为单个字符串、整数或浮点数的优点在于解码时:如果您遇到的只是字符串,或者特别是整数或浮点数,则需要了解一些有关数据的信息才能知道它是否是日期时间。作为字典,您可以存储元数据,以便可以自动解码,这就是json_tricks
您要做的。它也易于人类编辑。
免责声明:这是我做的。因为我遇到了同样的问题。
解决方案 23:
根据jjmontes 的回答,我使用了以下方法。对于Flask和flask-restful用户
# Get JSON string
jsonStr = json.dumps(my_dictionary, indent=1, sort_keys=True, default=str)
# Then convert the JSON string to a JSON object
return json.loads(jsonStr)
解决方案 24:
如果您在视图中使用结果,请确保返回正确的响应。根据 API,jsonify 执行以下操作:
使用 application/json mimetype 创建具有给定参数的 JSON 表示形式的响应。
要使用 json.dumps 模仿这种行为,您必须添加几行额外的代码。
response = make_response(dumps(sample, cls=CustomEncoder))
response.headers['Content-Type'] = 'application/json'
response.headers['mimetype'] = 'application/json'
return response
您还应该返回一个字典来完全复制 jsonify 的响应。因此,整个文件将如下所示
from flask import make_response
from json import JSONEncoder, dumps
class CustomEncoder(JSONEncoder):
def default(self, obj):
if set(['quantize', 'year']).intersection(dir(obj)):
return str(obj)
elif hasattr(obj, 'next'):
return list(obj)
return JSONEncoder.default(self, obj)
@app.route('/get_reps/', methods=['GET'])
def get_reps():
sample = ['some text', <datetime object>, 123]
response = make_response(dumps({'result': sample}, cls=CustomEncoder))
response.headers['Content-Type'] = 'application/json'
response.headers['mimetype'] = 'application/json'
return response
解决方案 25:
我的解决方案...
from datetime import datetime
import json
from pytz import timezone
import pytz
def json_dt_serializer(obj):
"""JSON serializer, by macm.
"""
rsp = dict()
if isinstance(obj, datetime):
rsp['day'] = obj.day
rsp['hour'] = obj.hour
rsp['microsecond'] = obj.microsecond
rsp['minute'] = obj.minute
rsp['month'] = obj.month
rsp['second'] = obj.second
rsp['year'] = obj.year
rsp['tzinfo'] = str(obj.tzinfo)
return rsp
raise TypeError("Type not serializable")
def json_dt_deserialize(obj):
"""JSON deserialize from json_dt_serializer, by macm.
"""
if isinstance(obj, str):
obj = json.loads(obj)
tzone = timezone(obj['tzinfo'])
tmp_dt = datetime(obj['year'],
obj['month'],
obj['day'],
hour=obj['hour'],
minute=obj['minute'],
second=obj['second'],
microsecond=obj['microsecond'])
loc_dt = tzone.localize(tmp_dt)
deserialize = loc_dt.astimezone(tzone)
return deserialize
好的,现在进行一些测试。
# Tests
now = datetime.now(pytz.utc)
# Using this solution
rsp = json_dt_serializer(now)
tmp = json_dt_deserialize(rsp)
assert tmp == now
assert isinstance(tmp, datetime) == True
assert isinstance(now, datetime) == True
# using default from json.dumps
tmp = json.dumps(datetime.now(pytz.utc), default=json_dt_serializer)
rsp = json_dt_deserialize(tmp)
assert isinstance(rsp, datetime) == True
# Lets try another timezone
eastern = timezone('US/Eastern')
now = datetime.now(eastern)
rsp = json_dt_serializer(now)
tmp = json_dt_deserialize(rsp)
print(tmp)
# 2015-10-22 09:18:33.169302-04:00
print(now)
# 2015-10-22 09:18:33.169302-04:00
# Wow, Works!
assert tmp == now
解决方案 26:
将转换date
为 string
date = str(datetime.datetime(somedatetimehere))
解决方案 27:
这是我将日期时间转换为 JSON 并转回的完整解决方案......
import calendar, datetime, json
def outputJSON(obj):
"""Default JSON serializer."""
if isinstance(obj, datetime.datetime):
if obj.utcoffset() is not None:
obj = obj - obj.utcoffset()
return obj.strftime('%Y-%m-%d %H:%M:%S.%f')
return str(obj)
def inputJSON(obj):
newDic = {}
for key in obj:
try:
if float(key) == int(float(key)):
newKey = int(key)
else:
newKey = float(key)
newDic[newKey] = obj[key]
continue
except ValueError:
pass
try:
newDic[str(key)] = datetime.datetime.strptime(obj[key], '%Y-%m-%d %H:%M:%S.%f')
continue
except TypeError:
pass
newDic[str(key)] = obj[key]
return newDic
x = {'Date': datetime.datetime.utcnow(), 34: 89.9, 12.3: 90, 45: 67, 'Extra': 6}
print x
with open('my_dict.json', 'w') as fp:
json.dump(x, fp, default=outputJSON)
with open('my_dict.json') as f:
my_dict = json.load(f, object_hook=inputJSON)
print my_dict
输出
{'Date': datetime.datetime(2013, 11, 8, 2, 30, 56, 479727), 34: 89.9, 45: 67, 12.3: 90, 'Extra': 6}
{'Date': datetime.datetime(2013, 11, 8, 2, 30, 56, 479727), 34: 89.9, 45: 67, 12.3: 90, 'Extra': 6}
JSON 文件
{"Date": "2013-11-08 02:30:56.479727", "34": 89.9, "45": 67, "12.3": 90, "Extra": 6}
这使我能够导入和导出字符串、整数、浮点数和日期时间对象。扩展其他类型应该不难。
解决方案 28:
我在使用 sqlalchemy 在类中编写序列化装饰器时收到了相同的错误消息。因此,不要这样做:
Class Puppy(Base):
...
@property
def serialize(self):
return { 'id':self.id,
'date_birth':self.date_birth,
...
}
我只是借用了 jgbarah 使用 isoformat() 的想法,并将原始值附加到 isoformat() 中,现在看起来像这样:
...
'date_birth':self.date_birth.isoformat(),
...
解决方案 29:
如果您想要自己的格式,可以快速修复
for key,val in sample.items():
if isinstance(val, datetime):
sample[key] = '{:%Y-%m-%d %H:%M:%S}'.format(val) #you can add different formating here
json.dumps(sample)
解决方案 30:
当我将Django模型对象外部化并转储为 JSON时,我遇到了同样的问题。
以下是解决方法。
def externalize(model_obj):
keys = model_obj._meta.get_all_field_names()
data = {}
for key in keys:
if key == 'date_time':
date_time_obj = getattr(model_obj, key)
data[key] = date_time_obj.strftime("%A %d. %B %Y")
else:
data[key] = getattr(model_obj, key)
return data