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.

Rolando
źródło
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.

jdi
źródło
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)
jjmontes
źródło
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 .

jgbarah
źródło
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.

Lenny
źródło
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() )
DA
źródło
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'
Jay Taylor
źródło
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)
Natim
źródło
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"}
kodowanie
źródło
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)
Fiatjaf
źródł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

davidhadas
źródło
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)) 
Saurabh Saha
źródło
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;

Cyker
źródło
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'}
Benyamin Jafari
źródło
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”}

ob92
źródło
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)

Sean Redmond
źródło
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()
Peter Graham
źródło
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.

AngelDown
źródło
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
reubano
źródło
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()
zhigang
źródło
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
macm
źródło
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.

Hovo
źródło
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)) 
Rana Nematollahi
źródło
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.

znak
źródło
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(),
                  ...
Treefish Zhang
źródło
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)
Wysypka
źródło
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ć.

ThunderBear
źródło
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
naren
źródło
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
Vinod Kumar
źródło
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.

MacSanhe
źródło
-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'> 
siedmiodniowa żałoba
źródło