J'ai un dict de base comme suit:
sample = {}
sample['title'] = "String"
sample['somedate'] = somedatetimehere
Lorsque j'essaie de faire jsonify(sample)
je reçois:
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 ne soit peut-être pas pertinent, les dictionnaires sont générés à partir de la récupération d'enregistrements de mongodb. Lorsque j'imprime str(sample['somedate'])
, le résultat est 2012-08-08 21:46:24.862000
.
La réponse originale convenait à la façon dont les champs "date" de MongoDB étaient représentés comme suit:
{"$date": 1506816000000}
Si vous souhaitez une solution Python générique pour la sérialisation de datetime
en json, consultez réponse de @ jjmontes pour une solution rapide ne nécessitant 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 vous aider avec 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 fournit un sérialiseur natif DjangoJSONEncoder
qui gère correctement ce type de problème.
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ée entre DjangoJSONEncoder
et l'utilisation d'un default
personnalisé comme celui-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 supprime 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.
Mon dump JSON rapide et sale qui mange des dates et tout:
json.dumps(my_dictionary, indent=4, sort_keys=True, default=str)
S'appuyant sur d'autres réponses, une solution simple basée sur un sérialiseur spécifique ne convertissant que les objets datetime.datetime
et datetime.date
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 on le voit, le code vérifie simplement si l'objet est de classe datetime.datetime
ou datetime.date
, puis utilise .isoformat()
pour en produire une version sérialisée, conformément au 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 déclencher 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 sont disponibles dans Section Utilisation de base de la documentation du module json .
Je viens de rencontrer ce problème et ma solution consiste à sous-classe 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)
Dans votre appel, faites quelque chose comme: json.dumps(yourobj, cls=DateTimeEncoder)
La .isoformat()
que j'ai tirée de l'une des réponses ci-dessus.
Convertir la date en chaîne
sample['somedate'] = str( datetime.utcnow() )
Pour ceux qui n'ont pas besoin ou ne veulent pas utiliser la bibliothèque pymongo pour cela .. vous pouvez facilement convertir les fichiers 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,))
Alors utilisez-le comme suit:
import datetime, json
print json.dumps(datetime.datetime.now(), default=default)
sortie:
'1365091796124'
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)
J'ai une application avec un problème similaire. Mon approche consistait à JSONize la valeur datetime comme une liste de 6 éléments (année, mois, jour, heure, minutes, secondes); vous pouvez utiliser microseconds sous forme de 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"}
Ce Q répète maintes et maintes fois - un moyen simple de corriger le module JSON de sorte que la sérialisation prenne en charge la date/heure.
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-ci avec datetime sérialisé sous forme d’isoformat.
json.dumps({'created':datetime.datetime.now()})
Résultat de la recherche: '{"created": "2015-08-26T14: 21: 31.853855"}'
Voir plus de détails et quelques mots de prudence sur: StackOverflow: date-heure JSON entre Python et JavaScript
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)
Ensuite, utilisez jsondumps
au lieu de json.dumps
. Il imprimera:
>>> jsondumps({'today': datetime.date.today()})
'{"today": "2013-07-30"}'
Si vous le souhaitez, vous pourrez ultérieurement y ajouter d’autres cas particuliers en modifiant simplement la méthode default
. 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)
Voici une solution simple pour résoudre le problème "datetime pas JSON sérialisable".
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"}
Vous devez utiliser la méthode .strftime()
sur la méthode .datetime.now()
pour la transformer en 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
Sortie:
Out[0]: {'a': 1, 'b': 2, 'time': '2017-10-31T15:16:30'}
Vous devez fournir une classe de codeur personnalisée avec le paramètre cls
de json.dumps
. Pour citer les docs :
>>> import json
>>> class ComplexEncoder(json.JSONEncoder):
... def default(self, obj):
... if isinstance(obj, complex):
... return [obj.real, obj.imag]
... return json.JSONEncoder.default(self, obj)
...
>>> dumps(2 + 1j, cls=ComplexEncoder)
'[2.0, 1.0]'
>>> ComplexEncoder().encode(2 + 1j)
'[2.0, 1.0]'
>>> list(ComplexEncoder().iterencode(2 + 1j))
['[', '2.0', ', ', '1.0', ']']
Ceci 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)
si vous utilisez python3.7, la meilleure solution consiste à utiliser datetime.isoformat()
et datetime.fromisoformat()
; ils travaillent avec des objets à la fois naïfs et conscients datetime
:
#!/usr/bin/env python3.7
from datetime import datetime
from datetime import timezone
from datetime import timedelta
import json
def default(obj):
if isinstance(obj, datetime):
return { '_isoformat': obj.isoformat() }
return super().default(obj)
def object_hook(obj):
_isoformat = obj.get('_isoformat')
if _isoformat is not None:
return datetime.fromisoformat(_isoformat)
return obj
if __== '__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))
sortie:
{"now": {"_isoformat": "2000-01-01T00:00:00-08:00"}}
True
si vous utilisez python3.6 ou une version antérieure et que vous vous souciez uniquement de la valeur de l'heure (et non du fuseau horaire), vous pouvez utiliser plutôt datetime.timestamp()
et datetime.fromtimestamp()
;
si vous utilisez python3.6 ou une version antérieure inférieure 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; Pour ce faire, le plus simple consiste à ajouter un autre champ _tzinfo
dans l'objet sérialisé.
enfin, méfiez-vous des précisions dans tous ces exemples;
La méthode json.dumps peut accepter un paramètre facultatif appelé default, censé être une fonction. Chaque fois que JSON essaie de convertir une valeur, il ne sait pas comment le convertir appelle 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))
Le moyen le plus simple de procéder consiste à modifier la partie de dict qui est au format date-heure en isoformat. Cette valeur sera effectivement une chaîne dans l'isoformat avec laquelle json convient.
v_dict = version.dict()
v_dict['created_at'] = v_dict['created_at'].isoformat()
En fait, c'est assez simple. Si vous devez souvent sérialiser les dates, utilisez-les en tant que 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 la sérialisation.
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 constater, le résultat est le même dans les deux cas. Seul le type est différent.
Si vous utilisez le résultat dans une vue, veillez à 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 type MIME 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 devriez é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
Voici ma solution complète de conversion de date/heure en JSON et retour ..
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
Sortie
{'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 objets de type chaînes, ints, floats et datetime. Il ne devrait pas être trop difficile d'étendre pour d'autres types.
Généralement, il existe plusieurs façons de sérialiser les dates/heures, comme par exemple:
Si la solution vous convient le mieux, le paquetage json_tricks gère les dates, heures et dates, 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 avez à faire c'est
`pip install json_tricks`
puis importez de json_tricks
au lieu de json
.
Le décodage présente l’avantage de ne pas la stocker sous forme de chaîne unique, int ou float: si vous ne rencontrez qu’une chaîne, en particulier int ou float, vous devez savoir quelque chose sur les données pour savoir s’il s’agit d’une date/heure. En tant que dicton, vous pouvez stocker des métadonnées afin de pouvoir les décoder automatiquement, ce que json_tricks
fait pour vous. C'est aussi facilement éditable pour les humains.
Disclaimer: c'est fait par moi. Parce que j'ai eu le même problème.
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 __== '__main__':
test()
Convertir la date
en string
date = str(datetime.datetime(somedatetimehere))
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)
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 en tant que
from datetime import datetime
depuis eval va se plaindre. Ou vous pouvez passer datetime en tant que paramètre à eval. En tout cas cela devrait marcher.
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
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 (), de sorte qu'elle ressemble maintenant à:
...
'date_birth':self.date_birth.isoformat(),
...
J'avais rencontré le même problème lors de l'externalisation de l'objet de modèle Django à exporter 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
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
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:
votre code doit localiser la méthode de sérialisation/désérialisation appropriée en fonction de l'objet python. Habituellement, le nom de classe complet est un bon identifiant.
Et ensuite, votre méthode ser/deser devrait être capable de transformer votre objet en un objet sérialisable Json normal, une combinaison de type générique python, dict, liste, chaîne, int, float. Et implémentez votre méthode de désinversement.