Как преодолеть "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