Jak przezwyciężyć „datetime.datetime not JSON serializable”?


739

Mam podstawowy dykt, jak następuje:

sample = {}
sample['title'] = "String"
sample['somedate'] = somedatetimehere

Kiedy próbuję to zrobić jsonify(sample), dostaję:

TypeError: datetime.datetime(2012, 8, 8, 21, 46, 24, 862000) is not JSON serializable

Co mogę zrobić, aby moja próbka słownika mogła przezwyciężyć powyższy błąd?

Uwaga: Chociaż może to nie być istotne, słowniki są generowane na podstawie wyszukiwania rekordów, z mongodbktórych, kiedy drukuję str(sample['somedate']), wynik jest 2012-08-08 21:46:24.862000.


1
Czy jest to konkretnie ogólnie python, czy może django?
— jdi

1
Technicznie jest to konkretnie python, nie używam django, ale pobieram rekordy z mongodb.
— Rolando


Używam mongoengine, ale jeśli pymongo ma lepsze sposoby na obejście tego lub przezwyciężenie tego, proszę powiedzieć.
— Rolando

3
Połączone pytanie zasadniczo mówi, aby nie próbować serializować obiektu datetime, ale raczej konwertować go na ciąg znaków we wspólnym formacie ISO przed serializacją.
— Thomas Kelley,

Odpowiedzi:


377

Zaktualizowano w 2018 r

Oryginalna odpowiedź uwzględniała sposób, w jaki pola „daty” MongoDB były reprezentowane jako:

{"$date": 1506816000000}

Jeśli potrzebujesz ogólnego rozwiązania Python do serializacji datetimedo json, sprawdź odpowiedź @jjmontes, aby znaleźć szybkie rozwiązanie, które nie wymaga żadnych zależności.


Ponieważ używasz mongoengine (na komentarze), a pymongo jest zależnością, pymongo ma wbudowane narzędzia do pomocy w serializacji json:
http://api.mongodb.org/python/1.10.1/api/bson/json_util.html

Przykładowe użycie (serializacja):

from bson import json_util
import json

json.dumps(anObject, default=json_util.default)

Przykładowe użycie (deserializacja):

json.loads(aJsonString, object_hook=json_util.object_hook)

Django

Django zapewnia natywny DjangoJSONEncoderserializator, który poprawnie radzi sobie z tego rodzaju.

Zobacz 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
)

Zauważyłem jedną różnicę między DjangoJSONEncoderużywaniem niestandardowego defaulttypu takiego:

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
)

Czy Django usuwa trochę danych:

 "last_login": "2018-08-03T10:51:42.990", # DjangoJSONEncoder 
 "last_login": "2018-08-03T10:51:42.990239", # default

W niektórych przypadkach może być konieczne zachowanie ostrożności.


3
Czy dobrą / złą praktyką jest mieszanie wielu bibliotek, tj. Posiadanie mongoengine do wstawiania dokumentów i pymongo do zapytań / pobierania?
— Rolando,

Nie jest to zła praktyka, po prostu implikuje pewną zależność od bibliotek używanych przez bibliotekę główną. Jeśli nie możesz osiągnąć tego, czego potrzebujesz od mongoengine, zejdź do pymongo. To samo z Django MongoDB. W późniejszym okresie próbujesz pozostać w ramach ORM django, aby utrzymać stan agnostyczny zaplecza. Ale czasami nie możesz zrobić tego, czego potrzebujesz w abstrakcji, więc upuszczasz warstwę. W tym przypadku jest to całkowicie niezwiązane z twoim problemem, ponieważ po prostu używasz metod użyteczności, aby towarzyszyć formatowi JSON.
— jdi

Próbuję tego z Flask i wydaje się, że używając json.dump, nie jestem w stanie umieścić wokół niego opakowania jsonify (), tak że zwraca on aplikację / json. Próba wykonania zwrotu jsonify (json.dumps (sample, default = json_util.default))
— Rolando

2
@ amit Nie chodzi tu tak bardzo o zapamiętywanie składni, ale o umiejętność czytania dokumentacji i przechowywania w mojej głowie wystarczającej ilości informacji, aby rozpoznać, gdzie i kiedy muszę ją ponownie pobrać. W takim przypadku można powiedzieć „Och, niestandardowy obiekt z jsonem”, a następnie szybko odświeżyć dane użycie
— jdi

2
@guyskk Nie śledziłem zmian w Bjson ani Mongo, odkąd napisałem to 5 lat temu. Ale jeśli chcesz kontrolować serializację daty i godziny, musisz napisać własną domyślną funkcję obsługi, jak pokazano w odpowiedzi udzielonej przez jgbarah
— jdi

616

Mój szybki i brudny zrzut JSON, który zjada daty i wszystko:

json.dumps(my_dictionary, indent=4, sort_keys=True, default=str)

13
To jest niesamowite, ale niestety nie rozumiem, co się stało? Czy ktoś może wyjaśnić tę odpowiedź?
— Kishor Pawar

61
@KishorPawar: defaultto funkcja stosowana do obiektów, których nie można serializować. W tym przypadku jest strtak, więc po prostu konwertuje wszystko, czego nie wie, na ciągi. Co jest świetne w przypadku serializacji, ale nie tak świetne podczas deserializacji (stąd „szybkie i brudne”), ponieważ wszystko mogło być ciągiem znaków bez ostrzeżenia, np. Funkcja lub tablica liczb.
— Mark

1
@Mark awesome. Dzięki. Przydatne, gdy znasz typ tych wartości, których nie można przekształcić do postaci szeregowej, takich jak daty.
— Kishor Pawar,

2
Dlaczego spędziłem całe życie, nie wiedząc o tym. :)
— Arel

1
@jjmontes, nie działa na wszystko, np json.dumps({():1,type(None):2},default=str)podbicia TypeError, nie może mieć typ lub krotki.
— alancalvitti

443

Opierając się na innych odpowiedziach, proste rozwiązanie oparte na konkretnym serializatorze, który po prostu konwertuje datetime.datetimei datetime.dateobiektów na ciągi.

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))

Jak widać, kod tylko kontrole, aby dowiedzieć się, czy obiekt jest klasy datetime.datetimelub datetime.date, a następnie wykorzystuje .isoformat()do produkcji odcinkach wersję nim, zgodnie z formatem ISO 8601, yyyy-MM-DDTgg: MM: SS (co jest łatwo dekodowane przez JavaScript ). Jeśli poszukiwane są bardziej złożone serializacje, zamiast str () można użyć innego kodu (przykłady można znaleźć w innych odpowiedziach na to pytanie). Kod kończy się zgłoszeniem wyjątku, aby poradzić sobie z przypadkiem, który jest wywoływany z typem, który nie jest serializowany.

Z tej funkcji json_serial można korzystać w następujący sposób:

from datetime import datetime
from json import dumps

print dumps(datetime.now(), default=json_serial)

Szczegółowe informacje na temat działania domyślnego parametru json.dumps można znaleźć w sekcji Podstawowe użycie dokumentacji modułu json .


5
tak, poprawna odpowiedź, ładniejszy import datetime i jeśli isinstance (obj, datetime.datetime), straciłem wiele czasu, ponieważ nie korzystałem z importu datetime datetime, zresztą dzięki
— Sérgio

12
ale to nie wyjaśnia, jak deserializować go odpowiednim typem, prawda?
— BlueTrin

2
Nie, @BlueTrin, nic o tym nie mówiło. W moim przypadku deserializuję w JavaScript, który działa od razu po wyjęciu z pudełka.
— jgbarah

1
Spowoduje to nieoczekiwane zachowanie, jeśli moduł json kiedykolwiek zaktualizuje się, aby uwzględnić serializację obiektów daty i godziny.
— Justin,

1
@serg Ale czas konwersji na UTC ujednoliciłby się 01:00:00+01:00i 02:00:00+00:00które nie powinny być takie same, w zależności od kontekstu. Odnoszą się one do tego samego punktu w czasie, ale przesunięcie może być istotnym aspektem wartości.
— Alfe

211

Właśnie napotkałem ten problem i moim rozwiązaniem jest podklasa 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)

W wywołaniu zrobić coś takiego: json.dumps(yourobj, cls=DateTimeEncoder)The .isoformat()dostałem od jednego z powyższych odpowiedzi.


22
wzrosła, ponieważ wdrożenie niestandardowego JSONEncodera powinno być właściwą drogą
— 3k-

25
To powinna być nie tylko najlepsza odpowiedź, ale także element zwykłego kodera json. Gdyby tylko dekodowanie było mniej dwuznaczne ...
— Joost

4
Dla osób korzystających z Django, patrz DjangoJSONEncoder. docs.djangoproject.com/en/dev/topics/serialization/…
— S. Kirby,

4
Super pomocny. Ostatnia linia może byćreturn super(DateTimeEncoder, self).default(o)
— Bob Stein

16
W Pythonie 3 ostatnia linia jest jeszcze prostsza:return super().default(o)
— ariddell 15.01.2018

124

Konwertuj datę na ciąg

sample['somedate'] = str( datetime.utcnow() )

10
I w jaki sposób mogę dokonać deserializacji w Pythonie?
— wobmene

62
Problem polega na tym, że wiele obiektów typu data-godzina jest głęboko osadzonych w strukturze danych lub są one losowe. To nie jest niezawodna metoda.
— Rebs

3
deserializacji: oDate = datetime.datetime.strptime(sDate, '%Y-%m-%d %H:%M:%S.%f'). Formaty uzyskane z: docs.python.org/2/library/datetime.html
— Roman

13
Przegłosowany, ponieważ ignoruje informacje o strefie czasowej. Pamiętaj, że .now()używa czasu lokalnego, bez zaznaczania tego. Przynajmniej .utcnow()należy użyć (a następnie dołączyć +0000 lub Z)
— Daniel F

1
@DanielF At least .utcnow() should be usedNiezupełnie, datetime.now(timezone.utc)jest zalecane, patrz ostrzeżenie w: docs.python.org/3.8/library/… .
— Toreno96

79

Dla innych, którzy nie potrzebują lub nie chcą w tym celu korzystać z biblioteki pymongo .. można łatwo uzyskać konwersję JSON datetime za pomocą tego małego fragmentu:

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,))

Następnie użyj go w następujący sposób:

import datetime, json
print json.dumps(datetime.datetime.now(), default=default)

wynik: 

'1365091796124'

1
Nie powinno millis=być wcięte wewnątrz instrukcji if? Prawdopodobnie lepiej jest również użyć str (obj), aby uzyskać format ISO, który moim zdaniem byłby bardziej powszechny.
— Rebs

Dlaczego chcesz, żeby było wcięte? Ten fragment kodu działa, a wynikowe dane wyjściowe można łatwo przekształcić z postaci szeregowej / przeanalizować z javascript.
— Jay Taylor

5
Ponieważ obj może nie być obiektem [czas, data, data / godzina]
— Rebs

2
twój przykład jest niepoprawny, jeśli lokalna strefa czasowa ma niezerowe przesunięcie UTC (większość z nich). datetime.now()zwraca czas lokalny (jako naiwny obiekt typu data-godzina), ale twój kod zakłada, że objjest w UTC, jeśli nie obsługuje strefy czasowej. Użyj datetime.utcnow()zamiast tego.
— jfs

1
Dostosowano go tak, aby pojawiał się błąd typu, jeśli obiekt nie jest rozpoznawany zgodnie z zaleceniami dokumentacji Pythona na docs.python.org/2/library/json.html#basic-usage .
— Jay Taylor,

40

Oto moje rozwiązanie:

# -*- 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)

Następnie możesz użyć go w ten sposób:

json.dumps(dictionnary, cls=DatetimeEncoder)

Zgodzić się. O wiele lepiej, przynajmniej poza kontekstem mongodb. Możesz to zrobić isinstance(obj, datetime.datetime)w TypeError, dodać więcej typów do obsługi i zakończyć za pomocą str(obj)lub repr(obj). I wszystkie zrzuty mogą po prostu wskazywać na tę wyspecjalizowaną klasę.
— JL Peyret

@Natim to rozwiązanie jest najlepsze. +1
— Souvik Ray

20

Mam aplikację z podobnym problemem; moim podejściem było JSONize wartości datetime jako listy 6-elementowej (rok, miesiąc, dzień, godzina, minuty, sekundy); możesz przejść do mikrosekund jako listy 7-elementowej, ale nie musiałem:

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)

produkuje:

{'somedate': datetime.datetime(2013, 8, 1, 16, 22, 45, 890000), 'title': 'String'}
{"somedate": [2013, 8, 1, 16, 22, 45], "title": "String"}

Nie działa, jeśli zaoszczędzony czas jest zapisywany przez wykonanie datetime.utcnow ()
— saurshaz,

1
Jaki błąd występuje w przypadku funkcji datetime.utcnow ()? Dla mnie to działa dobrze.
— kodowanie

17

Moje rozwiązanie (chyba mniej gadatliwe):

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)

Następnie użyj jsondumpszamiast json.dumps. Wydrukuje:

>>> jsondumps({'today': datetime.date.today()})
'{"today": "2013-07-30"}'

Chcę, później możesz dodać do tego inne specjalne przypadki za pomocą prostej zmiany defaultmetody. Przykład:

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)

1
Powinieneś użyć isinstance (o, (datetime.date, datetime.datetime,)). Prawdopodobnie nie zaszkodzi również dołączyć datetime.time.
— Rebs

Nie sądzę, żeby to było dobre rozwiązanie. Prawdopodobnie konwersje powinny zająć bardziej uprzywilejowane miejsce - a także bardziej zrozumiałe miejsce - w kodzie, abyś wiedział, do czego się konwertuje, kiedy umieszczasz rzeczy w bazie danych lub cokolwiek innego, zamiast robić wszystko przez przejrzysta funkcja. Ale nie wiem.
— fiatjaf

1
JSON nadaje się do szeregowania danych do późniejszego przetworzenia. Możesz nie wiedzieć dokładnie, co to za dane. I nie powinieneś. Serializacja JSON powinna po prostu działać. Podobnie jak konwersja Unicode na ascii powinna. Brak możliwości Pythona bez niejasnych funkcji sprawia, że ​​korzystanie z niego jest denerwujące. Sprawdzanie poprawności bazy danych to osobny problem IMO.
— Rebs

Nie, to nie powinno „po prostu działać”. Jeśli nie wiesz, jak nastąpiła serializacja i musisz później uzyskać dostęp do danych z innego programu / języka, jesteś zgubiony.
— fiatjaf

2
JSON jest powszechnie używany do łańcuchów, liczb całkowitych, liczb zmiennoprzecinkowych, dat (jestem pewien, że inni też używają waluty, temperatur). Ale data / godzina jest częścią standardowej biblioteki i powinna obsługiwać de / serializację. Gdyby nie to pytanie, nadal szukałbym ręcznie moich niewiarygodnie skomplikowanych obiektów BLS JSON (dla których nie zawsze tworzyłem strukturę) pod kątem dat i szeregowałbym je 1 do 1
— Rebs

16

To Q powtarza się raz po raz - prosty sposób na załatanie modułu json tak, aby serializacja obsługiwała datetime.

import json
import datetime

json.JSONEncoder.default = lambda self,obj: (obj.isoformat() if isinstance(obj, datetime.datetime) else None)

Następnie używaj serializacji json jak zawsze - tym razem z serializacją daty i godziny jako izoformatu.

json.dumps({'created':datetime.datetime.now()})

Wynik: „{„ utworzono ”:„ 2015-08-26T14: 21: 31.853855 ”}”

Zobacz więcej szczegółów i kilka ostrzeżeń na: StackOverflow: JSON datetime między Pythonem a JavaScript


Naszywka małpa FTW. Paskudną rzeczą jest oczywiście to, że modyfikuje to zachowanie modułu json w całej aplikacji, co może zaskoczyć innych w dużej aplikacji, dlatego należy na ogół używać ostrożnie imho.
— Jaap Versteegh

15

Metoda json.dumps może zaakceptować opcjonalny parametr o nazwie default, który ma być funkcją. Za każdym razem, gdy JSON próbuje przekonwertować wartość, nie wie, jak ją przekonwertować, wywoła przekazaną nam funkcję. Funkcja otrzyma przedmiotowy obiekt i oczekuje się, że zwróci reprezentację JSON obiektu.

def myconverter(o):
 if isinstance(o, datetime.datetime):
    return o.__str__()

print(json.dumps(d, default = myconverter)) 

14

jeśli używasz python3.7, najlepszym rozwiązaniem jest użycie datetime.isoformat()i datetime.fromisoformat(); pracują z datetimeobiektami zarówno naiwnymi, jak i świadomymi :

#!/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))

wynik:

{"now": {"_isoformat": "2000-01-01T00:00:00-08:00"}}
True

jeśli używasz Python3.6 lub niższej wersji i zależy ci tylko na wartości czasu (nie strefie czasowej), możesz użyć datetime.timestamp()i datetime.fromtimestamp()zamiast;

jeśli używasz Python3.6 lub niższej wersji i zależy ci na strefie czasowej, możesz to zrobić datetime.tzinfo, ale musisz samodzielnie serializować to pole; najłatwiej to zrobić, dodając kolejne pole _tzinfow serializowanym obiekcie;

na koniec strzeżcie się precyzji we wszystkich tych przykładach;


datetime.isoformat () jest również obecny w Pythonie 2.7: docs.python.org/2/library/…
— powlo

11

Powinieneś użyć .strftime()metody na .datetime.now()metodzie, aby uczynić ją metodą szeregowalną .

Oto przykład:

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

Wynik:

Out[0]: {'a': 1, 'b': 2, 'time': '2017-10-31T15:16:30'}

10

Oto proste rozwiązanie istniejącego problemu „dataetime 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)

Dane wyjściowe: -> {„data”: „2015-12-16T04: 48: 20.024609”}


8

Musisz podać niestandardową klasę enkodera z clsparametrem json.dumps. Cytując z dokumentów :

>>> 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', ']']

W tym przykładzie użyto liczb zespolonych, ale równie łatwo można utworzyć klasę do kodowania dat (z wyjątkiem tego, że JSON jest trochę rozmyślny na temat dat)


5

Najprostszym sposobem na to jest zmiana części nagrania w formacie datetime na izoformat. Ta wartość będzie efektywnie ciągiem znaków w izoformacie, z którym json jest w porządku.

v_dict = version.dict()
v_dict['created_at'] = v_dict['created_at'].isoformat()

5

W rzeczywistości jest to dość proste. Jeśli musisz często serializować daty, pracuj z nimi jako ciągami znaków. W razie potrzeby możesz łatwo przekonwertować je z powrotem jako obiekty daty i godziny.

Jeśli chcesz pracować głównie jako obiekty datetime, przekonwertuj je jako ciągi przed serializacją.

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'>

Jak widać, wynik jest taki sam w obu przypadkach. Tylko typ jest inny.


3

Jeśli używasz wyniku w widoku, pamiętaj o zwróceniu prawidłowej odpowiedzi. Zgodnie z interfejsem API, jsonify wykonuje następujące czynności:

Tworzy odpowiedź z reprezentacją JSON podanych argumentów o typie aplikacji / json.

Aby naśladować to zachowanie za pomocą json.dumps, musisz dodać kilka dodatkowych wierszy kodu.

response = make_response(dumps(sample, cls=CustomEncoder))
response.headers['Content-Type'] = 'application/json'
response.headers['mimetype'] = 'application/json'
return response

Powinieneś również zwrócić dyktando, aby w pełni replikować odpowiedź jsonify. Cały plik będzie więc wyglądał tak

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

1
Pytanie nie ma nic wspólnego z kolbą.
— Zoran Pavlovic

2
Pytanie dotyczy Pythona. Moja odpowiedź rozwiązuje pytanie za pomocą Pythona. OP nie powiedział, czy rozwiązanie powinno obejmować lub wykluczać niektóre biblioteki. Przydaje się również każdemu, kto czyta to pytanie i chce alternatywy pymongo.
— reubano

Są to zarówno kwestia o Pythonie i nie o kolby. Kolba nie jest nawet potrzebna w odpowiedzi na pytanie, więc sugeruję, aby ją usunąć.
— Zoran Pavlovic

3

Spróbuj tego z przykładem, aby go przeanalizować:

#!/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()

2

Moje rozwiązanie ...

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    

Ok, teraz kilka testów.

# 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

2

Oto moje pełne rozwiązanie do konwersji datetime na JSON iz powrotem.

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

Wynik

{'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}

Plik JSON

{"Date": "2013-11-08 02:30:56.479727", "34": 89.9, "45": 67, "12.3": 90, "Extra": 6}

To pozwoliło mi importować i eksportować ciągi, inty, zmiennoprzecinkowe i obiekty datetime. Nie powinno być trudno rozszerzyć na inne typy.


1
Eksploduje w Pythonie 3 z TypeError: 'str' does not support the buffer interface. To z powodu 'wb'trybu otwartego powinno być 'w'. Uderza również w deserializację, gdy mamy dane podobne do daty, takie jak, '0000891618-05-000338'ale nie pasujące do wzorca.
— omikron

2

Konwertuj date na string

date = str(datetime.datetime(somedatetimehere)) 

Odpowiedź jjmontes robi dokładnie to, ale bez konieczności robienia tego wprost dla każdej daty ...
— bluesummers

2

Zasadniczo istnieje szereg sposobów serializacji czasów danych, takich jak:

  1. Ciąg ISO, krótki i może zawierać informacje o strefie czasowej, np. Odpowiedź @ jgbarah
  2. Znacznik czasu (utracono dane strefy czasowej), np. Odpowiedź @ JayTaylor
  3. Słownik właściwości (w tym strefa czasowa).

Jeśli nie masz nic przeciwko temu, pakiet json_tricks obsługuje daty, godziny i czasy danych, w tym strefy czasowe.

from datetime import datetime
from json_tricks import dumps
foo = {'title': 'String', 'datetime': datetime(2012, 8, 8, 21, 46, 24, 862000)}
dumps(foo)

co daje:

{"title": "String", "datetime": {"__datetime__": null, "year": 2012, "month": 8, "day": 8, "hour": 21, "minute": 46, "second": 24, "microsecond": 862000}}

Więc wszystko, co musisz zrobić, to

`pip install json_tricks`

a następnie zaimportuj z json_trickszamiastjson .

Zaleta polegająca na tym, że nie jest przechowywana jako pojedynczy ciąg, liczba całkowita lub liczba zmiennoprzecinkowa, pojawia się podczas dekodowania: jeśli napotkasz tylko ciąg znaków, a szczególnie wartość całkowitą lub zmiennoprzecinkową, musisz wiedzieć coś o danych, aby wiedzieć, czy jest to data i godzina. W przypadku nagrania można przechowywać metadane, aby można je było dekodować automatycznie, czyli cojson_tricks dla Ciebie . Można go również łatwo edytować dla ludzi.

Zastrzeżenie: jest wykonane przeze mnie. Ponieważ miałem ten sam problem.


1

Otrzymałem ten sam komunikat o błędzie podczas pisania serializatora w klasie z sqlalchemy. Więc zamiast:

Class Puppy(Base):
    ...
    @property
    def serialize(self):
        return { 'id':self.id,
                 'date_birth':self.date_birth,
                  ...
                }

Po prostu pożyczyłem pomysł jgbarah na użycie isoformat () i dołączyłem pierwotną wartość do isoformat (), aby teraz wyglądało to tak:

                  ...
                 'date_birth':self.date_birth.isoformat(),
                  ...

1

Szybka poprawka, jeśli chcesz mieć własne formatowanie

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)

1

Jeśli jesteś po obu stronach komunikacji, możesz używać funkcji repr () i eval () wraz z 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))

Nie należy importować daty i godziny jako

from datetime import datetime

ponieważ eval będzie narzekać. Lub możesz przekazać datetime jako parametr do oceny. W każdym razie powinno to działać.


0

Napotkałem ten sam problem podczas uzewnętrzniania obiektu modelu django w celu zrzutu jako JSON. Oto jak możesz to rozwiązać.

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

0
def j_serial(o):     # self contained
    from datetime import datetime, date
    return str(o).split('.')[0] if isinstance(o, (datetime, date)) else None

Zastosowanie powyższego narzędzia:

import datetime
serial_d = j_serial(datetime.datetime.now())
if serial_d:
    print(serial_d)  # output: 2018-02-28 02:23:15

0

Superjson biblioteki może to zrobić. I możesz z łatwością dostosować serializator json do własnego obiektu Python, postępując zgodnie z tą instrukcją https://superjson.readthedocs.io/index.html#extend .

Ogólna koncepcja to:

Twój kod musi znaleźć właściwą metodę serializacji / deserializacji na podstawie obiektu Pythona. Zwykle pełna nazwa klasy jest dobrym identyfikatorem.

Następnie twoja metoda ser / deser powinna być w stanie przekształcić obiekt w zwykły obiekt szeregowalny Json, kombinacja ogólnego typu python, dict, list, string, int, float. I zaimplementuj swoją metodę dezerteracji odwrotnie.


-1

Może nie w 100% poprawne, ale jest to prosty sposób na serializację

#!/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'> 
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.