Comment surmonter «datetime.datetime non JSON sérialisable»?

743

J'ai un dict de base comme suit:

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

Quand j'essaye de faire, jsonify(sample)j'obtiens:

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

Que puis-je faire pour que mon exemple de dictionnaire puisse surmonter l'erreur ci-dessus?

Remarque: Bien que cela puisse ne pas être pertinent, les dictionnaires sont générés à partir de la récupération d'enregistrements d' mongodboù lorsque j'imprime str(sample['somedate']), la sortie l'est 2012-08-08 21:46:24.862000.

Rolando
la source
1
Est-ce spécifiquement python en général, ou peut-être Django?
jdi
1
Techniquement, il s'agit spécifiquement de python, je n'utilise pas django, mais je récupère les enregistrements de mongodb.
Rolando
duplication possible de datetime JSON entre Python et JavaScript
jdi
J'utilise mongoengine, mais si pymongo a de meilleures façons de contourner cela ou de surmonter cela, veuillez le dire.
Rolando
3
La question liée vous dit essentiellement de ne pas essayer de sérialiser l'objet datetime, mais plutôt de le convertir en une chaîne au format ISO commun avant de sérialiser.
Thomas Kelley

Réponses:

377

Mise à jour pour 2018

La réponse originale correspondait à la façon dont les champs "date" de MongoDB étaient représentés comme suit:

{"$date": 1506816000000}

Si vous voulez une solution générique Python pour la sérialisation datetimeen json, consultez la réponse de @jjmontes pour une solution rapide qui ne nécessite aucune dépendance.


Comme vous utilisez mongoengine (par commentaires) et pymongo est une dépendance, pymongo a des utilitaires intégrés pour aider à la sérialisation json:
http://api.mongodb.org/python/1.10.1/api/bson/json_util.html

Exemple d'utilisation (sérialisation):

from bson import json_util
import json

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

Exemple d'utilisation (désérialisation):

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

Django

Django fournit un DjangoJSONEncodersérialiseur natif qui traite correctement ce type.

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

J'ai remarqué une différence entre DjangoJSONEncoderet utiliser une coutume defaultcomme celle-ci:

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
)

Est-ce que Django dépouille un peu des données:

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

Donc, vous devrez peut-être faire attention à cela dans certains cas.

jdi
la source
3
Est-ce une bonne / mauvaise pratique de mélanger plusieurs bibliothèques, c'est-à-dire d'avoir mongoengine pour insérer des documents et pymongo pour interroger / récupérer?
Rolando
Ce n'est pas une mauvaise pratique, cela implique simplement une certaine dépendance des bibliothèques que votre bibliothèque principale utilise. Si vous ne pouvez pas accomplir ce dont vous avez besoin avec mongoengine, alors vous passez à pymongo. C'est la même chose avec Django MongoDB. Avec ce dernier, vous essayez de rester dans l'ORM django pour maintenir l'état agnostique du backend. Mais parfois, vous ne pouvez pas faire ce dont vous avez besoin dans l'abstraction, vous déposez donc un calque. Dans ce cas, son complètement sans rapport avec votre problème car vous utilisez simplement des méthodes utilitaires pour accompagner le format JSON.
jdi
J'essaie ceci avec Flask et il semble qu'en utilisant json.dump, je ne peux pas mettre un wrapper jsonify () autour de lui de sorte qu'il retourne dans application / json. Tentative de retour de jsonify (json.dumps (exemple, défaut = json_util.default))
Rolando
2
@amit Il ne s'agit pas tant de mémoriser la syntaxe, mais de bien lire la documentation et de stocker suffisamment d'informations dans ma tête pour reconnaître où et quand je dois la récupérer. Dans ce cas, on pourrait dire "Oh un objet personnalisé avec json" puis rafraîchir rapidement cette utilisation
jdi
2
@guyskk Je n'ai pas suivi les changements dans bjson ou mongo depuis que j'ai écrit cela il y a 5 ans. Mais si vous voulez contrôler la sérialisation de la date / heure, vous devez écrire votre propre fonction de gestionnaire par défaut comme illustré dans la réponse donnée par jgbarah
jdi
619

Mon vidage JSON rapide et sale qui mange des dates et tout:

json.dumps(my_dictionary, indent=4, sort_keys=True, default=str)
jjmontes
la source
14
C'est génial, mais malheureusement je n'ai pas compris ce qui s'est passé? Quelqu'un peut-il expliquer cette réponse?
Kishor Pawar
63
@KishorPawar: defaultest une fonction appliquée aux objets qui ne sont pas sérialisables. Dans ce cas, c'est le cas str, il convertit donc tout ce qu'il ne sait pas en chaînes. Ce qui est idéal pour la sérialisation mais pas si bien lors de la désérialisation (d'où le "rapide et sale") car tout ce qui aurait pu être enchaîné sans avertissement, par exemple une fonction ou un tableau numpy.
Mark
1
@Mark génial. Merci. Utile lorsque vous connaissez le type de ces valeurs non sérialisables comme les dates.
Kishor Pawar
2
Pourquoi suis-je allé toute ma vie sans le savoir. :)
Arel
1
@jjmontes, ne fonctionne pas pour tout, par exemple les json.dumps({():1,type(None):2},default=str)relances TypeError, ne peut pas avoir de type ou de tuple.
alancalvitti
443

En s'appuyant sur d'autres réponses, une solution simple basée sur un sérialiseur spécifique qui convertit datetime.datetimeet datetime.dateobjecte simplement en chaînes.

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

Comme vu, le code vérifie simplement si l'objet est de classe datetime.datetimeou datetime.date, puis utilise .isoformat()pour en produire une version sérialisée, selon le format ISO 8601, YYYY-MM-DDTHH: MM: SS (qui est facilement décodé par JavaScript ). Si des représentations sérialisées plus complexes sont recherchées, un autre code pourrait être utilisé à la place de str () (voir d'autres réponses à cette question pour des exemples). Le code finit par lever une exception, pour traiter le cas où il est appelé avec un type non sérialisable.

Cette fonction json_serial peut être utilisée comme suit:

from datetime import datetime
from json import dumps

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

Les détails sur le fonctionnement du paramètre par défaut de json.dumps se trouvent dans la section Utilisation de base de la documentation du module json .

jgbarah
la source
5
ouais la bonne réponse, plus jolie datetime import et si isinstance (obj, datetime.datetime), j'ai perdu beaucoup de temps car ne pas utiliser depuis datetime import datetime, de toute façon merci
Sérgio
12
mais cela n'explique pas comment le désérialiser avec le bon type, n'est-ce pas?
BlueTrin
2
Non, @BlueTrin, rien n'a été dit à ce sujet. Dans mon cas, je désérialise en JavaScript, ce qui fonctionne hors de la boîte.
jgbarah
1
Cela entraînera un comportement inattendu si le module json se met à jour pour inclure la sérialisation des objets datetime.
Justin
1
@serg Mais la conversion des temps en UTC unifierait 01:00:00+01:00et 02:00:00+00:00qui ne sont pas censés être les mêmes, selon le contexte. Ils se réfèrent bien entendu au même moment, mais le décalage peut être un aspect pertinent de la valeur.
Alfe
211

Je viens de rencontrer ce problème et ma solution est de sous json.JSONEncoder- classer :

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)

Dans votre appel, faites quelque chose comme: json.dumps(yourobj, cls=DateTimeEncoder)Le .isoformat()j'ai reçu d'une des réponses ci-dessus.

lenny
la source
22
augmenté parce que la mise en œuvre d'un JSONEncoder personnalisé devrait être la bonne façon de procéder
3k-
25
Non seulement cela devrait être la première réponse, cela devrait faire partie de l'encodeur json standard. Si seulement le décodage était moins ambigu ..
Joost
4
Pour ceux qui utilisent Django, voir DjangoJSONEncoder. docs.djangoproject.com/en/dev/topics/serialization/…
S. Kirby
4
Super utile. Dernière ligne pourrait êtrereturn super(DateTimeEncoder, self).default(o)
Bob Stein
16
Avec Python 3, la dernière ligne est encore plus simple:return super().default(o)
ariddell
124

Convertir la date en chaîne

sample['somedate'] = str( datetime.utcnow() )
DA
la source
10
Et comment pourrais-je le désérialiser en Python?
wobmene
62
Le problème est que si vous avez de nombreux objets datetime intégrés profondément dans une structure de données, ou s'ils sont aléatoires. Ce n'est pas une méthode fiable.
Rebs
3
désérialiser: oDate = datetime.datetime.strptime(sDate, '%Y-%m-%d %H:%M:%S.%f'). Formats obtenus à partir de: docs.python.org/2/library/datetime.html
Roman
13
Voté car il ignore les informations de fuseau horaire. Gardez à l'esprit que .now()l'heure locale est utilisée, sans l'indiquer. Au moins .utcnow()devrait être utilisé (puis un +0000 ou Z ajouté)
Daniel F
1
@DanielF At least .utcnow() should be usedPas exactement, datetime.now(timezone.utc)est recommandé, voir l'avertissement dans: docs.python.org/3.8/library/… .
Toreno96
79

Pour ceux qui n'ont pas besoin ou ne veulent pas utiliser la bibliothèque pymongo pour cela .. vous pouvez réaliser facilement la conversion JSON datetime avec ce petit extrait:

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

Ensuite, utilisez-le comme ceci:

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

production: 

'1365091796124'
Jay Taylor
la source
1
Ne doit pas millis=être mis en retrait à l'intérieur de l'instruction if? Il est également préférable d'utiliser str (obj) pour obtenir le format ISO qui, je pense, est plus courant.
Rebs
Pourquoi voudriez-vous qu'il soit en retrait? Cet extrait fonctionne et la sortie résultante peut facilement être désérialisée / analysée à partir de javascript.
Jay Taylor
5
Parce que obj n'est peut-être pas un objet [time, date, datetime]
Rebs
2
votre exemple est incorrect si le fuseau horaire local a un décalage UTC différent de zéro (la plupart d'entre eux). datetime.now()renvoie l'heure locale (comme un objet datetime naïf) mais votre code suppose qu'il objest en UTC s'il n'est pas sensible au fuseau horaire. Utilisez datetime.utcnow()plutôt.
jfs
1
Ajusté pour déclencher une erreur de type si obj n'est pas reconnu selon la recommandation de la documentation Python sur docs.python.org/2/library/json.html#basic-usage .
Jay Taylor
40

Voici ma solution:

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

Ensuite, vous pouvez l'utiliser comme ça:

json.dumps(dictionnary, cls=DatetimeEncoder)
Natim
la source
se mettre d'accord. Beaucoup mieux, du moins hors du contexte mongodb. Vous pouvez faire isinstance(obj, datetime.datetime)dans TypeError, ajouter plus de types à gérer et terminer avec str(obj)ou repr(obj). Et tous vos clichés peuvent simplement pointer vers cette classe spécialisée.
JL Peyret
@Natim cette solution est la meilleure. +1
Souvik Ray
20

J'ai une application avec un problème similaire; mon approche était de JSONize la valeur datetime comme une liste de 6 éléments (année, mois, jour, heure, minutes, secondes); vous pouvez aller en microsecondes en tant que liste de 7 éléments, mais je n'avais pas besoin de:

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)

produit:

{'somedate': datetime.datetime(2013, 8, 1, 16, 22, 45, 890000), 'title': 'String'}
{"somedate": [2013, 8, 1, 16, 22, 45], "title": "String"}
codingatty
la source
Ne fonctionne pas si le temps économisé est enregistré en faisant datetime.utcnow ()
saurshaz
1
Quelle erreur voyez-vous avec datetime.utcnow ()? Cela fonctionne bien pour moi.
codingatty
17

Ma solution (avec moins de verbosité, je pense):

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)

Utilisez ensuite jsondumpsau lieu de json.dumps. Il imprimera:

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

Si vous le souhaitez, plus tard, vous pouvez ajouter d'autres cas spéciaux à cela avec une simple torsion de la defaultméthode. Exemple:

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
la source
1
Vous devez utiliser isinstance (o, (datetime.date, datetime.datetime,)). Cela ne ferait probablement pas de mal d'inclure datetime.time aussi.
Rebs
Je ne pense plus que ce soit une bonne solution. Les conversions devraient probablement prendre une place plus privilégiée - et aussi plus compréhensible - dans votre code, afin que vous sachiez à quoi vous vous convertissez lorsque vous mettez des choses dans une base de données, ou autre chose, au lieu que tout soit fait par un fonction transparente. Mais je ne sais pas.
fiatjaf
1
JSON est bon pour sérialiser des données pour un traitement ultérieur. Vous ne savez peut-être pas exactement quelles sont ces données. Et vous ne devriez pas en avoir besoin. La sérialisation de JSON devrait simplement fonctionner. Tout comme la conversion d'unicode en ascii devrait. L'incapacité de Python à le faire sans fonctions obscures le rend ennuyeux à utiliser. La validation de la base de données est un problème distinct de l'OMI.
Rebs
Non, cela ne devrait pas "simplement fonctionner". Si vous ne savez pas comment la sérialisation s'est produite et devez accéder aux données ultérieurement à partir d'un autre programme / langue, vous êtes perdu.
fiatjaf
2
JSON est couramment utilisé pour les chaînes, les entiers, les flottants, les dates (je suis sûr que d'autres utilisent la monnaie, les températures, généralement aussi). Mais datetime fait partie de la bibliothèque standard et devrait prendre en charge la désérialisation / sérialisation. Si ce n'était pas pour cette question, je rechercherais toujours manuellement mes blobs json incroyablement complexes (dont je n'ai pas toujours créé la structure) pour les dates et les sérialiser 1 par 1.
Rebs
16

Ce Q se répète maintes et maintes fois - un moyen simple de patcher le module json de telle sorte que la sérialisation prend en charge datetime.

import json
import datetime

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

Utilisez la sérialisation json comme vous le faites toujours - cette fois avec datetime étant sérialisé en isoformat.

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

Résultat: '{"créé": "2015-08-26T14: 21: 31.853855"}'

Voir plus de détails et quelques mises en garde sur: StackOverflow: datetime JSON entre Python et JavaScript

davidhadas
la source
Patch de singe FTW. La chose désagréable est bien sûr que cela modifie le comportement du module json dans toute votre application, ce qui peut surprendre les autres dans une grande application, donc devrait généralement être utilisé avec précaution à mon humble avis.
Jaap Versteegh
15

La méthode json.dumps peut accepter un paramètre facultatif appelé default qui devrait être une fonction. Chaque fois que JSON essaie de convertir une valeur, il ne sait pas comment la convertir appellera la fonction que nous lui avons transmise. La fonction recevra l'objet en question et devrait renvoyer la représentation JSON de l'objet.

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

print(json.dumps(d, default = myconverter)) 
Saurabh Saha
la source
15

si vous utilisez python3.7, alors la meilleure solution est d'utiliser datetime.isoformat()et datetime.fromisoformat(); ils travaillent avec des datetimeobjets à la fois naïfs et conscients :

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

production:

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

si vous utilisez python3.6 ou inférieur et que vous ne vous souciez que de la valeur de temps (pas du fuseau horaire), vous pouvez utiliser datetime.timestamp()et à la datetime.fromtimestamp()place;

si vous utilisez python3.6 ou inférieur et que vous vous souciez du fuseau horaire, vous pouvez l'obtenir via datetime.tzinfo, mais vous devez sérialiser ce champ par vous-même; la façon la plus simple de procéder consiste à ajouter un autre champ _tzinfodans l'objet sérialisé;

enfin, méfiez-vous des précisions dans tous ces exemples;

Cyker
la source
datetime.isoformat () est également présent dans Python 2.7: docs.python.org/2/library/…
powlo
11

Vous devez utiliser .strftime()méthode sur .datetime.now()méthode pour en faire une méthode sérialisable .

Voici un exemple:

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

Production:

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

Voici une solution simple pour surmonter le problème "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)

Sortie: -> {"date": "2015-12-16T04: 48: 20.024609"}

ob92
la source
8

Vous devez fournir une classe d'encodeur personnalisée avec le clsparamètre de json.dumps. Pour citer des documents :

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

Cela utilise des nombres complexes comme exemple, mais vous pouvez tout aussi facilement créer une classe pour encoder des dates (sauf que je pense que JSON est un peu flou à propos des dates)

Sean Redmond
la source
5

La manière la plus simple de le faire est de remplacer la partie du dict au format datetime par isoformat. Cette valeur sera effectivement une chaîne en isoformat avec laquelle json est d'accord.

v_dict = version.dict()
v_dict['created_at'] = v_dict['created_at'].isoformat()
Peter Graham
la source
5

En fait, c'est assez simple. Si vous devez souvent sérialiser des dates, utilisez-les comme des chaînes. Vous pouvez facilement les reconvertir en objets datetime si nécessaire.

Si vous devez travailler principalement en tant qu'objets datetime, convertissez-les en chaînes avant de sérialiser.

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

Comme vous pouvez le voir, la sortie est la même dans les deux cas. Seul le type est différent.

AngelDown
la source
3

Si vous utilisez le résultat dans une vue, assurez-vous de renvoyer une réponse correcte. Selon l'API, jsonify effectue les opérations suivantes:

Crée une réponse avec la représentation JSON des arguments donnés avec un mimetype application / json.

Pour imiter ce comportement avec json.dumps, vous devez ajouter quelques lignes de code supplémentaires.

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

Vous devez également renvoyer un dict pour répliquer entièrement la réponse de jsonify. Ainsi, le fichier entier ressemblera à ceci

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
la source
1
La question n'a rien à voir avec le flacon.
Zoran Pavlovic
2
La question concerne le python. Ma réponse résout la question en utilisant python. L'OP n'a pas précisé si la solution devait inclure ou exclure certaines bibliothèques. Il est également utile à toute personne lisant cette question qui souhaite une alternative à pymongo.
reubano
Ils s'interrogent à la fois sur Python et non sur Flask. Le flacon n'est même pas nécessaire dans votre réponse à la question, je vous suggère donc de le supprimer.
Zoran Pavlovic
3

Essayez celui-ci avec un exemple pour l'analyser:

#!/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
la source
2

Ma solution ...

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, maintenant quelques tests.

# 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
la source
2

Voici ma solution complète pour convertir datetime en JSON et vice-versa.

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

Production

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

Fichier JSON

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

Cela m'a permis d'importer et d'exporter des chaînes, des entiers, des flottants et des objets datetime. Cela ne devrait pas être trop difficile à étendre pour d'autres types.

Hovo
la source
1
Il explose en Python 3 avec TypeError: 'str' does not support the buffer interface. C'est à cause du 'wb'mode ouvert, devrait l'être 'w'. Il souffle également dans la désérialisation lorsque nous avons des données similaires à ce jour, '0000891618-05-000338'mais pas de modèle correspondant.
omikron
2

Convertissez le date en string

date = str(datetime.datetime(somedatetimehere)) 
Rana Nematollahi
la source
jjmontes answer fait exactement cela, mais sans la nécessité de le faire explicitement pour chaque date ...
bluesummers
2

En général, il existe plusieurs façons de sérialiser les heures de données, comme:

  1. Chaîne ISO, courte et peut inclure des informations de fuseau horaire, par exemple la réponse de @ jgbarah
  2. Horodatage (les données de fuseau horaire sont perdues), par exemple la réponse de @ JayTaylor
  3. Dictionnaire des propriétés (y compris le fuseau horaire).

Si vous êtes d'accord avec la dernière façon, le package json_tricks gère les dates, les heures et les heures, y compris les fuseaux horaires.

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

qui donne:

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

Donc tout ce que vous devez faire c'est

`pip install json_tricks`

puis importez depuis json_tricksau lieu de json.

L'avantage de ne pas le stocker comme une seule chaîne, int ou float vient lors du décodage: si vous rencontrez juste une chaîne ou surtout int ou float, vous devez savoir quelque chose sur les données pour savoir s'il s'agit d'un datetime. En tant que dict, vous pouvez stocker des métadonnées afin qu'elles puissent être décodées automatiquement, ce qui est json_tricksfait pour vous. Il est également facilement modifiable pour les humains.

Avertissement: il est fait par moi. Parce que j'avais le même problème.

marque
la source
1

J'ai reçu le même message d'erreur lors de l'écriture du décorateur de sérialisation dans une classe avec sqlalchemy. Donc au lieu de:

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

J'ai simplement emprunté l'idée de jgbarah d'utiliser isoformat () et ajouté la valeur d'origine avec isoformat (), pour qu'elle ressemble maintenant à:

                  ...
                 'date_birth':self.date_birth.isoformat(),
                  ...
Treefish Zhang
la source
1

Une solution rapide si vous voulez votre propre formatage

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)
Arash
la source
1

Si vous êtes des deux côtés de la communication, vous pouvez utiliser les fonctions repr () et eval () avec 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))

Vous ne devez pas importer datetime comme

from datetime import datetime

depuis eval se plaindra. Ou vous pouvez passer datetime comme paramètre à eval. Dans tous les cas, cela devrait fonctionner.

ThunderBear
la source
0

J'avais rencontré le même problème lors de l'externalisation de l'objet modèle django pour le vider en JSON. Voici comment vous pouvez le résoudre.

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
la source
0
def j_serial(o):     # self contained
    from datetime import datetime, date
    return str(o).split('.')[0] if isinstance(o, (datetime, date)) else None

Utilisation de l'utilitaire ci-dessus:

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

Cette bibliothèque superjson peut le faire. Et vous pouvez facilement personnaliser le sérialiseur json pour votre propre objet Python en suivant cette instruction https://superjson.readthedocs.io/index.html#extend .

Le concept général est le suivant:

votre code doit localiser la bonne méthode de sérialisation / désérialisation basée sur l'objet python. Habituellement, le nom de classe complet est un bon identificateur.

Et puis, votre méthode ser / deser devrait être capable de transformer votre objet en un objet sérialisable Json standard, une combinaison de type python générique, dict, list, string, int, float. Et implémentez votre méthode deser à l'envers.

MacSanhe
la source
-1

Je ne suis peut-être pas 100% correct, mais c'est le moyen simple de sérialiser

#!/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'> 
Shiva
la source