Datetime JSON entre Python et JavaScript

393

Je veux envoyer un objet datetime.datetime sous forme sérialisée à partir de Python en utilisant JSON et désérialiser en JavaScript en utilisant JSON. Quelle est la meilleure façon de procéder?

Peter Mortensen
la source
Préférez-vous utiliser une bibliothèque ou voulez-vous la coder vous-même?
guettli

Réponses:

370

Vous pouvez ajouter le paramètre 'default' à json.dumps pour gérer ceci:

date_handler = lambda obj: (
    obj.isoformat()
    if isinstance(obj, (datetime.datetime, datetime.date))
    else None
)
json.dumps(datetime.datetime.now(), default=date_handler)
'"2010-04-20T20:08:21.634121"'

Qui est ISO 8601 format .

Une fonction de gestionnaire par défaut plus complète:

def handler(obj):
    if hasattr(obj, 'isoformat'):
        return obj.isoformat()
    elif isinstance(obj, ...):
        return ...
    else:
        raise TypeError, 'Object of type %s with value of %s is not JSON serializable' % (type(obj), repr(obj))

Mise à jour: sortie ajoutée de type ainsi que de valeur.
Mise à jour: gérer également la date

JT.
la source
11
Le problème est que si vous avez d'autres objets dans list / dict, ce code les convertira en None.
Tomasz Wysocki
5
json.dumps ne saura pas non plus comment les convertir, mais l'exception est supprimée. Malheureusement, un correctif lambda sur une ligne a ses défauts. Si vous préférez qu'une exception soit levée sur les inconnues (ce qui est une bonne idée), utilisez la fonction que j'ai ajoutée ci-dessus.
JT.
9
le format de sortie complet devrait également avoir un fuseau horaire ... et isoformat () ne fournit pas cette fonctionnalité ... vous devez donc vous assurer d'ajouter ces informations sur la chaîne avant de revenir
Nick Franceschina
3
C'est la meilleure façon de procéder. Pourquoi cela n'a-t-il pas été sélectionné comme réponse?
Brendon Crawford
16
Le lambda peut être adapté pour appeler l'implémentation de base sur des types non datetime, donc TypeError peut être levée si nécessaire:dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime) else json.JSONEncoder().default(obj)
Pascal Bourque
81

Pour les projets multilingues , j'ai découvert que les chaînes contenant des dates RfC 3339 sont la meilleure solution. Une date RfC 3339 ressemble à ceci:

  1985-04-12T23:20:50.52Z

Je pense que la plupart du format est évident. La seule chose quelque peu inhabituelle peut être le "Z" à la fin. Il signifie GMT / UTC. Vous pouvez également ajouter un décalage de fuseau horaire comme +02: 00 pour CEST (Allemagne en été). Personnellement, je préfère tout garder en UTC jusqu'à ce qu'il s'affiche.

Pour l'affichage, les comparaisons et le stockage, vous pouvez le laisser sous forme de chaîne dans toutes les langues. Si vous avez besoin de la date pour les calculs, convertissez-la facilement en un objet de date natif dans la plupart des langues.

Générez donc le JSON comme ceci:

  json.dump(datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ'))

Malheureusement, le constructeur Date de Javascript n'accepte pas les chaînes RfC 3339 mais il existe de nombreux analyseurs disponibles sur Internet.

huTools.hujson essaie de gérer les problèmes d'encodage les plus courants que vous pourriez rencontrer dans le code Python, y compris les objets date / datetime tout en gérant correctement les fuseaux horaires.

max
la source
17
Ce mécanisme de formatage de date est pris en charge de manière native, à la fois par datetime: datetime.isoformat () et par simplejson, qui videront les datetimeobjets sous forme de isoformatchaînes par défaut. Pas besoin de strftimepiratage manuel .
jrk
9
@jrk - Je ne reçois pas de conversion automatique des datetimeobjets en isoformatchaîne. Pour moi, les simplejson.dumps(datetime.now())rendementsTypeError: datetime.datetime(...) is not JSON serializable
kostmo
6
json.dumps(datetime.datetime.now().isoformat())c'est là que la magie opère.
jathanism
2
La beauté de simplejson est que si j'ai une structure de données complexe, il la analysera et la transformera en JSON. Si je dois faire json.dumps (datetime.datetime.now (). Isoformat ()) pour chaque objet datetime, je le perds. Y'a t'il un moyen d'arranger cela?
andrewrk
1
superjoe30: voir stackoverflow.com/questions/455580/… sur la façon de le faire
max
67

Je l'ai résolu.

Disons que vous avez un objet datetime Python, d , créé avec datetime.now (). Sa valeur est:

datetime.datetime(2011, 5, 25, 13, 34, 5, 787000)

Vous pouvez le sérialiser en JSON en tant que chaîne de date / heure ISO 8601:

import json    
json.dumps(d.isoformat())

L'exemple d'objet datetime serait sérialisé comme suit:

'"2011-05-25T13:34:05.787000"'

Cette valeur, une fois reçue dans la couche Javascript, peut construire un objet Date:

var d = new Date("2011-05-25T13:34:05.787000");

Depuis Javascript 1.8.5, les objets Date ont une méthode toJSON, qui retourne une chaîne dans un format standard. Pour sérialiser l'objet Javascript ci-dessus en JSON, la commande serait donc:

d.toJSON()

Ce qui vous donnerait:

'2011-05-25T20:34:05.787Z'

Cette chaîne, une fois reçue en Python, pourrait être désérialisée en un objet datetime:

datetime.strptime('2011-05-25T20:34:05.787Z', '%Y-%m-%dT%H:%M:%S.%fZ')

Cela se traduit par l'objet datetime suivant, qui est le même que celui avec lequel vous avez commencé et donc correct:

datetime.datetime(2011, 5, 25, 20, 34, 5, 787000)
user240515
la source
50

À l'aide de json, vous pouvez sous-classer JSONEncoder et remplacer la méthode default () pour fournir vos propres sérialiseurs personnalisés:

import json
import datetime

class DateTimeJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        else:
            return super(DateTimeJSONEncoder, self).default(obj)

Ensuite, vous pouvez l'appeler comme ceci:

>>> DateTimeJSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'
ramen
la source
7
Amélioration mineure - utilisation obj.isoformat(). Vous pouvez également utiliser l'appel le plus courant dumps(), qui prend d'autres arguments utiles (comme indent): simplejson.dumps (myobj, cls = JSONEncoder, ...)
rcoup
3
Parce que cela appellerait la méthode du parent de JSONEncoder, pas la méthode du parent de DateTimeJSONEncoder. IE, vous monteriez deux niveaux.
Brian Arsuaga
30

Voici une solution assez complète pour l'encodage et le décodage récursifs des objets datetime.datetime et datetime.date à l'aide du jsonmodule de bibliothèque standard . Cela nécessite Python> = 2.6 car le %fcode de format dans la chaîne de format datetime.datetime.strptime () n'est pris en charge que depuis lors. Pour la prise en charge de Python 2.5, supprimez le %fet supprimez les microsecondes de la chaîne de date ISO avant d'essayer de le convertir, mais vous perdrez la précision des microsecondes, bien sûr. Pour l'interopérabilité avec les chaînes de date ISO d'autres sources, qui peuvent inclure un nom de fuseau horaire ou un décalage UTC, vous devrez peut-être également supprimer certaines parties de la chaîne de date avant la conversion. Pour un analyseur complet des chaînes de date ISO (et de nombreux autres formats de date), consultez le module de dateutil tiers .

Le décodage ne fonctionne que lorsque les chaînes de date ISO sont des valeurs dans une notation d'objet littéral JavaScript ou dans des structures imbriquées dans un objet. Les chaînes de date ISO, qui sont des éléments d'un tableau de niveau supérieur, ne seront pas décodées.

C'est à dire que cela fonctionne:

date = datetime.datetime.now()
>>> json = dumps(dict(foo='bar', innerdict=dict(date=date)))
>>> json
'{"innerdict": {"date": "2010-07-15T13:16:38.365579"}, "foo": "bar"}'
>>> loads(json)
{u'innerdict': {u'date': datetime.datetime(2010, 7, 15, 13, 16, 38, 365579)},
u'foo': u'bar'}

Et cela aussi:

>>> json = dumps(['foo', 'bar', dict(date=date)])
>>> json
'["foo", "bar", {"date": "2010-07-15T13:16:38.365579"}]'
>>> loads(json)
[u'foo', u'bar', {u'date': datetime.datetime(2010, 7, 15, 13, 16, 38, 365579)}]

Mais cela ne fonctionne pas comme prévu:

>>> json = dumps(['foo', 'bar', date])
>>> json
'["foo", "bar", "2010-07-15T13:16:38.365579"]'
>>> loads(json)
[u'foo', u'bar', u'2010-07-15T13:16:38.365579']

Voici le code:

__all__ = ['dumps', 'loads']

import datetime

try:
    import json
except ImportError:
    import simplejson as json

class JSONDateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, (datetime.date, datetime.datetime)):
            return obj.isoformat()
        else:
            return json.JSONEncoder.default(self, obj)

def datetime_decoder(d):
    if isinstance(d, list):
        pairs = enumerate(d)
    elif isinstance(d, dict):
        pairs = d.items()
    result = []
    for k,v in pairs:
        if isinstance(v, basestring):
            try:
                # The %f format code is only supported in Python >= 2.6.
                # For Python <= 2.5 strip off microseconds
                # v = datetime.datetime.strptime(v.rsplit('.', 1)[0],
                #     '%Y-%m-%dT%H:%M:%S')
                v = datetime.datetime.strptime(v, '%Y-%m-%dT%H:%M:%S.%f')
            except ValueError:
                try:
                    v = datetime.datetime.strptime(v, '%Y-%m-%d').date()
                except ValueError:
                    pass
        elif isinstance(v, (dict, list)):
            v = datetime_decoder(v)
        result.append((k, v))
    if isinstance(d, list):
        return [x[1] for x in result]
    elif isinstance(d, dict):
        return dict(result)

def dumps(obj):
    return json.dumps(obj, cls=JSONDateTimeEncoder)

def loads(obj):
    return json.loads(obj, object_hook=datetime_decoder)

if __name__ == '__main__':
    mytimestamp = datetime.datetime.utcnow()
    mydate = datetime.date.today()
    data = dict(
        foo = 42,
        bar = [mytimestamp, mydate],
        date = mydate,
        timestamp = mytimestamp,
        struct = dict(
            date2 = mydate,
            timestamp2 = mytimestamp
        )
    )

    print repr(data)
    jsonstring = dumps(data)
    print jsonstring
    print repr(loads(jsonstring))
Chris Arndt
la source
Si vous imprimez la date comme datetime.datetime.utcnow().isoformat()[:-3]+"Z"elle sera exactement comme ce que JSON.stringify () produit en javascript
w00t
24

Si vous êtes certain que seul Javascript consommera le JSON, je préfère passer Datedirectement les objets Javascript .

La ctime()méthode sur les datetimeobjets renverra une chaîne que l'objet Date Javascript peut comprendre.

import datetime
date = datetime.datetime.today()
json = '{"mydate":new Date("%s")}' % date.ctime()

Javascript sera heureux de l'utiliser comme un littéral d'objet, et vous avez votre objet Date intégré.

Triptyque
la source
12
JSON techniquement non valide, mais il s'agit d'un littéral d'objet JavaScript valide. (Par souci de principe , je mettrais le Content-Type text / javascript au lieu de l' application / JSON.) Si le consommateur toujours et pour toujours être seulement une implémentation JavaScript, alors oui, cela est assez élégant. Je l'utiliserais.
système PAUSE
13
.ctime()est un TRÈS mauvais moyen de transmettre des informations sur le temps, .isoformat()c'est beaucoup mieux. Ce .ctime()qui est de jeter le fuseau horaire et l'heure d'été comme s'ils n'existaient pas. Cette fonction devrait être supprimée.
Evgeny
Des années plus tard: ne pensez jamais à faire ça. Cela ne fonctionnera que si vous évaluez votre json en Javascript, ce que vous ne devriez vraiment pas ...
domenukk
11

Tard dans le jeu ... :)

Une solution très simple consiste à patcher le module json par défaut. Par exemple:

import json
import datetime

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

Maintenant, vous pouvez utiliser json.dumps () comme s'il avait toujours pris en charge datetime ...

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

Cela a du sens si vous avez besoin que cette extension du module json démarre toujours et que vous ne vouliez pas changer la façon dont vous ou d'autres utilisez la sérialisation json (dans le code existant ou non).

Notez que certains peuvent considérer les correctifs de bibliothèques de cette manière comme une mauvaise pratique. Une attention particulière doit être portée au cas où vous souhaiteriez étendre votre application de plusieurs manières - dans ce cas, je suggère d'utiliser la solution par ramen ou JT et de choisir l'extension json appropriée dans chaque cas.

davidhadas
la source
6
Cela mange silencieusement des objets non sérialisables et les transforme en None. Vous voudrez peut-être lever une exception à la place.
Blender
6

Pas grand chose à ajouter à la réponse du wiki communautaire, à l'exception de l' horodatage !

Javascript utilise le format suivant:

new Date().toJSON() // "2016-01-08T19:00:00.123Z"

Côté Python (pour le json.dumpsgestionnaire, voir les autres réponses):

>>> from datetime import datetime
>>> d = datetime.strptime('2016-01-08T19:00:00.123Z', '%Y-%m-%dT%H:%M:%S.%fZ')
>>> d
datetime.datetime(2016, 1, 8, 19, 0, 0, 123000)
>>> d.isoformat() + 'Z'
'2016-01-08T19:00:00.123000Z'

Si vous omettez ce Z, les frameworks frontaux comme angular ne peuvent pas afficher la date dans le fuseau horaire local du navigateur:

> $filter('date')('2016-01-08T19:00:00.123000Z', 'yyyy-MM-dd HH:mm:ss')
"2016-01-08 20:00:00"
> $filter('date')('2016-01-08T19:00:00.123000', 'yyyy-MM-dd HH:mm:ss')
"2016-01-08 19:00:00"
user1338062
la source
4

Côté python:

import time, json
from datetime import datetime as dt
your_date = dt.now()
data = json.dumps(time.mktime(your_date.timetuple())*1000)
return data # data send to javascript

Côté javascript:

var your_date = new Date(data)

où les données proviennent de python

Coulé
la source
4

Mon conseil est d'utiliser une bibliothèque. Il y en a plusieurs disponibles sur pypi.org.

J'utilise celui-ci, ça marche bien: https://pypi.python.org/pypi/asjson

guettli
la source
0

Apparemment, le «bon» format de date JSON (bien JavaScript) est 2012-04-23T18: 25: 43.511Z - UTC et «Z». Sans cela, JavaScript utilisera le fuseau horaire local du navigateur Web lors de la création d'un objet Date () à partir de la chaîne.

Pour un temps "naïf" (ce que Python appelle un temps sans fuseau horaire et cela suppose qu'il est local), le ci-dessous forcera le fuseau horaire local afin qu'il puisse ensuite être correctement converti en UTC:

def default(obj):
    if hasattr(obj, "json") and callable(getattr(obj, "json")):
        return obj.json()
    if hasattr(obj, "isoformat") and callable(getattr(obj, "isoformat")):
        # date/time objects
        if not obj.utcoffset():
            # add local timezone to "naive" local time
            # /programming/2720319/python-figure-out-local-timezone
            tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
            obj = obj.replace(tzinfo=tzinfo)
        # convert to UTC
        obj = obj.astimezone(timezone.utc)
        # strip the UTC offset
        obj = obj.replace(tzinfo=None)
        return obj.isoformat() + "Z"
    elif hasattr(obj, "__str__") and callable(getattr(obj, "__str__")):
        return str(obj)
    else:
        print("obj:", obj)
        raise TypeError(obj)

def dump(j, io):
    json.dump(j, io, indent=2, default=default)

Pourquoi est-ce si difficile.

cagney
la source
0

Pour la conversion de date Python en JavaScript, l'objet date doit être au format ISO spécifique, c'est-à-dire au format ISO ou au numéro UNIX. Si le format ISO manque d'informations, vous pouvez d'abord convertir au numéro Unix avec Date.parse. De plus, Date.parse fonctionne également avec React tandis que la nouvelle Date peut déclencher une exception.

Si vous disposez d'un objet DateTime sans millisecondes, les éléments suivants doivent être pris en compte. :

  var unixDate = Date.parse('2016-01-08T19:00:00') 
  var desiredDate = new Date(unixDate).toLocaleDateString();

L'exemple de date peut également être une variable dans l'objet result.data après un appel d'API.

Pour les options pour afficher la date dans le format souhaité (par exemple pour afficher les longs jours de la semaine), consultez le document MDN .

Patrick
la source