web-dev-qa-db-fra.com

Comment surmonter "datetime.datetime pas JSON sérialisable"?

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.

583
Rolando

Mis à jour pour 2018

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

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.

307
jdi

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

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

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 .

420
jgbarah

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.

191
lenny

Convertir la date en chaîne

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

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'
78
Jay Taylor

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

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

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

17
davidhadas

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

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

10
ob92

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'}
9
Benyamin Jafari

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)

8
Sean Redmond

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;

7
Cyker

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)) 
6
Saurabh Saha

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()
5
Peter Graham

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.

4
AngelDown

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
3
reubano

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.

3
Hovo

Généralement, il existe plusieurs façons de sérialiser les dates/heures, comme par exemple:

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

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.

2
Mark

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()
2
zhigang

Convertir la date en string

date = str(datetime.datetime(somedatetimehere)) 
2
Rana Nematollahi

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

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.

1
ThunderBear

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
1
macm

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(),
                  ...
1
Treefish Zhang

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
0
naren
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
0
Vinod Kumar

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.

0
MacSanhe