Как преодолеть "datetime.datetime не JSON serializable"?
У меня есть базовый dict следующим образом:
sample = {}
sample['title'] = "String"
sample['somedate'] = somedatetimehere
Когда я пытаюсь сделать jsonify(sample)
, я получаю:
TypeError: datetime.datetime(2012, 8, 8, 21, 46, 24, 862000) is not JSON serializable
Что я могу сделать так, чтобы мой словарь мог преодолеть ошибку выше?
Примечание. Хотя это может и не быть релевантным, словари генерируются из поиска записей из mongodb, где, когда я печатаю str(sample['somedate'])
, вывод 2012-08-08 21:46:24.862000
.
Ответы
Ответ 1
Обновлено для 2018
Исходный ответ соответствовал тому, как поля даты в MongoDB были представлены как:
{"$date": 1506816000000}
Если вы хотите универсальное решение Python для сериализации datetime
в json, посмотрите ответ @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 предоставляет собственный сериализатор 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
и использованием пользовательских 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
Таким образом, в некоторых случаях вам может потребоваться быть осторожным с этим.
Ответ 2
Мой быстрый и грязный свал JSON, который ест даты и все:
json.dumps(my_dictionary, indent=4, sort_keys=True, default=str)
Ответ 3
Основываясь на других ответах, простое решение, основанное на определенном сериализаторе, который просто преобразует объекты 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.
Ответ 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
Вот мое решение:
# -*- coding: utf-8 -*-
import json
class DatetimeEncoder(json.JSONEncoder):
def default(self, obj):
try:
return super(DatetimeEncoder, obj).default(obj)
except TypeError:
return str(obj)
Затем вы можете использовать его следующим образом:
json.dumps(dictionnary, cls=DatetimeEncoder)
Ответ 8
У меня есть приложение с аналогичной проблемой; мой подход состоял в том, чтобы JSONize значение datetime как список из 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"}
Ответ 9
Этот Q снова и снова повторяется - простой способ исправить модуль json, чтобы сериализация поддерживала дату и время.
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" }'
См. более подробную информацию и некоторые предостережения:
fooobar.com/questions/7691/...
Ответ 10
Мое решение (с меньшей детализацией, я думаю):
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)
Ответ 11
Вот простое решение для более позднего "datetime not JSON serializable"
проблема.
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" }
Ответ 12
Вы должны использовать .strftime()
метода .datetime.now()
чтобы сделать его как сериализуемым методом.
Вот пример:
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'}
Ответ 13
если вы используете python3.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() }
return super().default(obj)
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
если вы используете python3.6 или ниже, и вас интересует только значение времени (не
часовой пояс), то вы можете использовать datetime.timestamp()
и
datetime.fromtimestamp()
вместо;
если вы используете python3.6 или ниже, и вам небезразличен часовой пояс, то
вы можете получить его через datetime.tzinfo
, но вы должны сериализовать это поле
самостоятельно; Самый простой способ сделать это - добавить еще одно поле _tzinfo
в поле
сериализованный объект;
наконец, остерегайтесь точности во всех этих примерах;
Ответ 14
Вы должны указать собственный класс энкодера с параметром cls
json.dumps
. Цитировать из docs:
>>> 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 немного нечеткий о датах)
Ответ 15
Метод json.dumps может принимать необязательный параметр с именем default, который, как ожидается, будет функцией. Каждый раз, когда JSON пытается преобразовать значение, он не знает, как его преобразовать, он вызовет функцию, которую мы передали ей. Функция получит объект, о котором идет речь, и ожидается, что оно вернет представление объекта JSON.
def myconverter(o):
if isinstance(o, datetime.datetime):
return o.__str__()
print(json.dumps(d, default = myconverter))
Ответ 16
Самый простой способ сделать это - изменить часть dict, которая находится в формате datetime, в isoformat. Это значение будет эффективно представлять собой строку в isoformat, с которой json в порядке.
v_dict = version.dict()
v_dict['created_at'] = v_dict['created_at'].isoformat()
Ответ 17
На самом деле это довольно просто. Если вам нужно часто сериализовать даты, тогда работайте с ними как строки. Вы можете легко преобразовать их в качестве объектов datetime, если это необходимо.
Если вам нужно работать в основном как объекты datetime, а затем конвертируйте их как строки перед сериализацией.
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'>
Как вы можете видеть, выход в обоих случаях одинаковый. Только тип отличается.
Ответ 18
Если вы используете результат в представлении, обязательно верните правильный ответ. Согласно API, jsonify выполняет следующие действия:
Создает ответ с представлением JSON данных аргументов с типом приложения /json.
Чтобы подражать этому поведению с помощью json.dumps, вам нужно добавить несколько дополнительных строк кода.
response = make_response(dumps(sample, cls=CustomEncoder))
response.headers['Content-Type'] = 'application/json'
response.headers['mimetype'] = 'application/json'
return response
Вы также должны вернуть dict для полной репликации ответа 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
Ответ 19
Вот мое полное решение для преобразования даты и времени в 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}
Это позволило мне импортировать и экспортировать строки, объекты int, float и datetime.
Не должно быть трудно распространяться на другие типы.
Ответ 20
Попробуйте этот пример с примером для его анализа:
#!/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()
Ответ 21
Преобразуйте date
в string
date = str(datetime.datetime(somedatetimehere))
Ответ 22
Как правило, существует несколько способов сериализации datetimes, например:
- Строка ISO, краткая и может включать информацию о часовом поясе, например. @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
.
Преимущество не хранить его как одну строку, int или float возникает при декодировании: если вы столкнулись только с строкой или особенно с int или float, вам нужно знать что-то о данных, чтобы узнать, является ли это datetime. Как dict, вы можете хранить метаданные, чтобы их можно было автоматически декодировать, что и делает json_tricks
для вас. Он также легко редактируется для людей.
Отказ от ответственности: это сделано мной. Потому что у меня была такая же проблема.
Ответ 23
Мое решение...
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
Ответ 24
У меня получилось такое же сообщение об ошибке при написании декоратора сериализации внутри класса с 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(),
...
Ответ 25
Быстрое исправление, если вы хотите, чтобы ваше собственное форматирование
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)
Ответ 26
Если вы находитесь по обе стороны сообщения, вы можете использовать функции repr() и eval() вместе с json.
import datetime, json
dt = datetime.datetime.now()
print("This is now: {}".format(dt))
dt1 = json.dumps(repr(dt))
print("This is serialised: {}".format(dt1))
dt2 = json.loads(dt1)
print("This is loaded back from json: {}".format(dt2))
dt3 = eval(dt2)
print("This is the same object as we started: {}".format(dt3))
print("Check if they are equal: {}".format(dt == dt3))
Вы не должны импортировать datetime как
from datetime import datetime
так как eval будет жаловаться. Или вы можете передать datetime в качестве параметра для eval. В любом случае это должно сработать.
Ответ 27
Я столкнулся с такой же проблемой при экстернализации объекта модели 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
Ответ 28
Либо укажите как даты в mysql, так и в коде python json как String или как дату, так и дату и время.
Он работал у меня, когда я преобразовал тип mysql в String.
Ответ 29
Мое решение состояло в том, чтобы использовать время EPOCH (это число), так как для моего использования не требовалось, чтобы конечный пользователь читал время в JSON. Было так много EASIER работать с эпохой.
Ответ 30
def j_serial(o): # self contained
from datetime import datetime, date
return str(o).split('.')[0] if isinstance(o, (datetime, date)) else None
Использование вышеуказанной утилиты:
import datetime
serial_d = j_serial(datetime.datetime.now())
if serial_d:
print(serial_d) # output: 2018-02-28 02:23:15