У меня есть базовый 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
.
Исходный ответ соответствовал тому, как поля даты в 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
Таким образом, вам может потребоваться быть осторожным с этим в некоторых случаях.
Django MongoDB
. В последнем случае вы пытаетесь оставаться в рамках django ORM, чтобы поддерживать независимое состояние бэкенда. Но иногда вы не можете делать то, что вам нужно в абстракции, поэтому вы опускаете слой. В этом случае это совершенно не связано с вашей проблемой, поскольку вы просто используете служебные методы для сопровождения формата JSON.
Основываясь на других ответах, простое решение, основанное на определенном сериализаторе, который просто преобразует объекты 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.
Мой быстрый и грязный свал JSON, который ест даты и все:
json.dumps(my_dictionary, indent=4, sort_keys=True, default=str)
default
это функция, применяемая к объектам, которые не сериализуются. В этом случае это str
, поэтому он просто конвертирует все, что не знает, в строки. Который отлично подходит для сериализации, но не так хорош при десериализации (отсюда и «быстрая и грязная»), так как все может быть строкой без предупреждения, например, функция или массив.
Я только что столкнулся с этой проблемой, и мое решение заключается в подклассе 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()
Я получил один из ответов выше.
Преобразование даты в строку
sample['somedate'] = str( datetime.utcnow() )
Для тех, кто не нуждается или хочет использовать библиотеку 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'
millis=
не следует millis=
в оператор if? Также, вероятно, лучше использовать str (obj) для получения формата ISO, который, я думаю, более распространен.
Вот мое решение:
# -*- 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)
isinstance(obj, datetime.datetime)
в TypeError, добавить дополнительные типы для обработки и в конце получить str(obj)
или repr(obj)
. И все ваши свалки могут просто указывать на этот специализированный класс.
У меня есть приложение с аналогичной проблемой; мой подход состоял в том, чтобы 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"}
Этот 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" }'
См. более подробную информацию и некоторые предостережения: https://stackoverflow.com/questions/455580/json-datetime-between-python-and-javascript
Мое решение (с меньшей детализацией, я думаю):
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)
Вот простое решение для более позднего "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" }
Вы должны указать собственный класс энкодера с параметром 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 немного нечеткий о датах)
Вы должны использовать .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'}
Самый простой способ сделать это - изменить часть dict, которая находится в формате datetime, в isoformat. Это значение будет эффективно представлять собой строку в isoformat, с которой json в порядке.
v_dict = version.dict()
v_dict['created_at'] = v_dict['created_at'].isoformat()
Вот мое полное решение для преобразования даты и времени в 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. Не должно быть трудно распространяться на другие типы.
TypeError: 'str' does not support the buffer interface
. Это из-за открытого режима 'wb'
, должно быть 'w'
. Это также происходит при десериализации, когда у нас есть данные, похожие на дату, такие как '0000891618-05-000338'
но не соответствующие шаблону.
Если вы используете результат в представлении, обязательно верните правильный ответ. Согласно 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
pymongo
.
если вы используете 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
в сериализованном объекте;
наконец, остерегайтесь указаний во всех этих примерах;
Метод json.dumps может принимать необязательный параметр с именем default, который, как ожидается, будет функцией. Каждый раз, когда JSON пытается преобразовать значение, он не знает, как его преобразовать, он вызовет функцию, которую мы передали ей. Функция получит объект, о котором идет речь, и ожидается, что оно вернет представление объекта JSON.
def myconverter(o):
if isinstance(o, datetime.datetime):
return o.__str__()
print(json.dumps(d, default = myconverter))
Попробуйте этот пример с примером для его анализа:
#!/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()
Как правило, существует несколько способов сериализации datetimes, например:
Если вы в порядке с последним способом, 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
для вас. Он также легко редактируется для людей.
Отказ от ответственности: это сделано мной. Потому что у меня была такая же проблема.
Преобразуйте date
в string
date = str(datetime.datetime(somedatetimehere))
Если вы находитесь по обе стороны сообщения, вы можете использовать функции 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. В любом случае это должно сработать.
Быстрое исправление, если вы хотите, чтобы ваше собственное форматирование
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)
У меня получилось такое же сообщение об ошибке при написании декоратора сериализации внутри класса с 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(),
...
Мое решение...
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
На самом деле это довольно просто. Если вам нужно часто сериализовать даты, тогда работайте с ними как строки. Вы можете легко преобразовать их в качестве объектов 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'>
Как вы можете видеть, выход в обоих случаях одинаковый. Только тип отличается.
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
Мое решение состояло в том, чтобы использовать время EPOCH (это число), так как для моего использования не требовалось, чтобы конечный пользователь читал время в JSON. Было так много EASIER работать с эпохой.
Либо укажите как даты в mysql, так и в коде python json как String или как дату, так и дату и время. Он работал у меня, когда я преобразовал тип mysql в String.
Я столкнулся с такой же проблемой при экстернализации объекта модели 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
Я не могу на 100% исправить, но, это простой способ сделать сериализацию
#!/usr/bin/python
import datetime,json
sampledict = {}
sampledict['a'] = "some string"
sampledict['b'] = datetime.datetime.now()
print sampledict # output : {'a': 'some string', 'b': datetime.datetime(2017, 4, 15, 5, 15, 34, 652996)}
#print json.dumps(sampledict)
'''
output :
Traceback (most recent call last):
File "./jsonencodedecode.py", line 10, in <module>
print json.dumps(sampledict)
File "/usr/lib/python2.7/json/__init__.py", line 244, in dumps
return _default_encoder.encode(obj)
File "/usr/lib/python2.7/json/encoder.py", line 207, in encode
chunks = self.iterencode(o, _one_shot=True)
File "/usr/lib/python2.7/json/encoder.py", line 270, in iterencode
return _iterencode(o, 0)
File "/usr/lib/python2.7/json/encoder.py", line 184, in default
raise TypeError(repr(o) + " is not JSON serializable")
TypeError: datetime.datetime(2017, 4, 15, 5, 16, 17, 435706) is not JSON serializable
'''
sampledict['b'] = datetime.datetime.now().strftime("%B %d, %Y %H:%M %p")
afterdump = json.dumps(sampledict)
print afterdump #output : {"a": "some string", "b": "April 15, 2017 05:18 AM"}
print type(afterdump) #<type 'str'>
afterloads = json.loads(afterdump)
print afterloads # output : {u'a': u'some string', u'b': u'April 15, 2017 05:18 AM'}
print type(afterloads) # output :<type 'dict'>