962 votes

Comment surmonter le problème "datetime.datetime not JSON serializable" ?

J'ai une dictée de base comme suit :

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

Quand j'essaie de faire jsonify(sample) J'ai compris :

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

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

Note : Bien que cela puisse ne pas être pertinent, les dictionnaires sont générés à partir de l'extraction d'enregistrements dans les bases de données de l'UE. mongodb où lorsque j'imprime str(sample['somedate']) la sortie est 2012-08-08 21:46:24.862000 .

1 votes

S'agit-il spécifiquement de python en général, ou éventuellement de django ?

2 votes

Techniquement, c'est spécifiquement python, je n'utilise pas django, mais je récupère des enregistrements à partir de mongodb.

0 votes

489voto

jgbarah Points 113

En se basant sur d'autres réponses, une solution simple basée sur un sérialiseur spécifique qui convertit simplement datetime.datetime y datetime.date en chaînes de caractères.

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 on le voit, le code vérifie simplement si l'objet est de la classe datetime.datetime o datetime.date et utilise ensuite .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 les autres réponses à cette question pour des exemples). Le code se termine en levant une exception, pour gérer 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 la façon dont le paramètre par défaut de json.dumps fonctionne peuvent être trouvés dans Section Utilisation de base de la documentation du module json .

6 votes

Oui la bonne réponse, plus jolie import datetime et if isinstance(obj, datetime.datetime) , j'ai perdu beaucoup de temps parce que je n'ai pas utilisé from datetime import datetime , de toute façon merci

16 votes

Mais cela n'explique pas comment le désérialiser avec le bon type, n'est-ce pas ?

3 votes

Non, @BlueTrin , rien n'a été dit à ce sujet. Dans mon cas, je désérialise en JavaScript, ce qui fonctionne d'emblée.

480voto

jdi Points 38029

Mise à jour pour 2018

La réponse initiale tenait compte de la façon dont les champs "date" de MongoDB étaient représentés :

{"$date": 1506816000000}

Si vous souhaitez une solution Python générique pour la sérialisation des données datetime en json, consultez La réponse de @jjmontes pour une solution rapide qui ne nécessite aucune dépendance.


Comme vous utilisez mongoengine (selon les commentaires) et que 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 une fonction native DjangoJSONEncoder sérialiseur qui traite ce genre de choses correctement.

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
)

Une différence que j'ai remarqué entre DjangoJSONEncoder et l'utilisation d'un default comme ça :

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
)

C'est que Django enlève un peu de données :

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

Il faut donc faire attention à cela dans certains cas.

4 votes

Est-ce une bonne/mauvaise pratique de mélanger plusieurs bibliothèques, par exemple d'avoir mongoengine pour l'insertion de documents et pymongo pour les requêtes/récupérations ?

0 votes

Ce n'est pas une mauvaise pratique, cela implique simplement une certaine dépendance vis-à-vis 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 . Dans ce dernier cas, vous essayez de rester dans l'ORM de django pour maintenir un état agnostique du backend. Mais parfois vous ne pouvez pas faire ce dont vous avez besoin dans l'abstraction, alors vous descendez d'une couche. Dans ce cas, cela n'a rien à voir avec votre problème puisque vous utilisez simplement des méthodes utilitaires pour accompagner le format JSON.

0 votes

J'essaie ceci avec Flask et il semble qu'en utilisant json.dump, je suis incapable de mettre un wrapper jsonify() autour de lui de sorte qu'il retourne en application/json. J'essaie de faire return jsonify(json.dumps(sample, default=json_util.default))

134voto

D.A Points 1319

Convertir la date en une chaîne de caractères

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

11 votes

Et comment pourrais-je le désérialiser en Python ?

69 votes

Le problème se pose lorsque de nombreux objets de type datetime sont intégrés profondément dans une structure de données, ou lorsqu'ils sont aléatoires. Cette méthode n'est pas fiable.

3 votes

Pour désérialiser : oDate = datetime.datetime.strptime(sDate, '%Y-%m-%d %H:%M:%S.%f') . Formats obtenus auprès de : docs.python.org/2/library/datetime.html

82voto

Jay Taylor Points 3262

Pour ceux qui n'ont pas besoin ou ne veulent pas utiliser la bibliothèque pymongo pour cela, vous pouvez réaliser la conversion date-heure JSON facilement 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 suit :

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

sortie : 

'1365091796124'

1 votes

Il ne faut pas millis= être indentée à l'intérieur de l'instruction if ? Il est aussi probablement préférable d'utiliser str(obj) pour obtenir le format ISO, qui me semble plus courant.

0 votes

Pourquoi voudriez-vous qu'il soit en retrait ? Cet extrait fonctionne et la sortie résultante peut facilement être désérialisée/parsée à partir de javascript.

5 votes

Parce que obj peut ne pas être un objet [time, date, datetime].

21voto

codingatty Points 312

J'ai une application avec un problème similaire ; mon approche consistait à JSONiser la valeur de la date sous forme d'une liste de 6 éléments (année, mois, jour, heure, minutes, secondes) ; on pourrait passer aux microsecondes sous forme d'une liste de 7 éléments, mais je n'en avais pas besoin :

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"}

0 votes

Ne fonctionne pas si l'heure enregistrée est sauvegardée en faisant datetime.utcnow()

1 votes

Quelle erreur constatez-vous avec datetime.utcnow() ? Cela fonctionne bien pour moi.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X