Datetime JSON tra Python e JavaScript


393

Voglio inviare un oggetto datetime.datetime in forma serializzata da Python usando JSON e deserializzare in JavaScript usando JSON. Qual è il modo migliore per farlo?


Preferisci usare una libreria o vuoi codificarlo tu stesso?
Guettli,

Risposte:


370

È possibile aggiungere il parametro 'default' a json.dumps per gestire questo:

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

Che è il formato ISO 8601 .

Una funzione di gestione predefinita più completa:

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

Aggiornamento: aggiunto l'output di tipo e valore.
Aggiornamento: gestire anche la data


11
Il problema è che se hai altri oggetti nella lista / dict questo codice li convertirà in Nessuno.
Tomasz Wysocki,

5
json.dumps non saprà neanche come convertirli, ma l'eccezione viene soppressa. Purtroppo una correzione lambda a una riga ha i suoi difetti. Se preferisci avere un'eccezione sollevata sugli incogniti (che è una buona idea) usa la funzione che ho aggiunto sopra.
JT.

9
il formato di output completo dovrebbe avere anche il fuso orario su di esso ... e isoformat () non fornisce questa funzionalità ... quindi assicurati di aggiungere quelle informazioni sulla stringa prima di tornare
Nick Franceschina

3
Questo è il modo migliore per andare. Perché questo non è stato selezionato come risposta?
Brendon Crawford,

16
Lambda può essere adattato per chiamare l'implementazione di base su tipi non datetime, quindi TypeError può essere sollevato se necessario:dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime) else json.JSONEncoder().default(obj)
Pascal Bourque

81

Per i progetti in più lingue, ho scoperto che le stringhe contenenti date RfC 3339 sono il modo migliore per procedere. Una data RfC 3339 si presenta così:

  1985-04-12T23:20:50.52Z

Penso che la maggior parte del formato sia ovvio. L'unica cosa in qualche modo insolita potrebbe essere la "Z" alla fine. Sta per GMT / UTC. Puoi anche aggiungere un fuso orario come +02: 00 per CEST (Germania in estate). Personalmente preferisco tenere tutto in UTC fino a quando non viene visualizzato.

Per la visualizzazione, i confronti e l'archiviazione è possibile lasciarlo in formato stringa in tutte le lingue. Se è necessaria la data per i calcoli, è possibile riconvertirla in un oggetto data nativo nella maggior parte delle lingue.

Quindi genera il JSON in questo modo:

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

Sfortunatamente, il costruttore Date di Javascript non accetta le stringhe RfC 3339 ma ci sono molti parser disponibili su Internet.

huTools.hujson tenta di gestire i problemi di codifica più comuni che potresti riscontrare nel codice Python, inclusi gli oggetti data / datetime, gestendo correttamente i fusi orari.


17
Questo meccanismo di formattazione della data è supportato in modo nativo, sia da datetime: datetime.isoformat () che da simplejson, che scaricherà gli datetimeoggetti come isoformatstringhe per impostazione predefinita. Non c'è bisogno di strftimehacking manuale .
jrk,

9
@jrk - Non sto ottenendo la conversione automatica dagli datetimeoggetti alla isoformatstringa. Per me, simplejson.dumps(datetime.now())reseTypeError: datetime.datetime(...) is not JSON serializable
kostmo

6
json.dumps(datetime.datetime.now().isoformat())è dove accade la magia.
jatanismo

2
Il bello di simplejson è che se ho una struttura dati complessa, la analizzerà e la trasformerà in JSON. Se devo fare json.dumps (datetime.datetime.now (). Isoformat ()) per ogni oggetto datetime, lo perdo. C'è un modo per risolvere questo problema?
andrewrk,

1
superjoe30: vedi stackoverflow.com/questions/455580/... su come farlo
max

67

L'ho capito.

Supponiamo che tu abbia un oggetto datetime Python, d , creato con datetime.now (). Il suo valore è:

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

È possibile serializzarlo su JSON come stringa datetime ISO 8601:

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

L'oggetto datetime di esempio verrebbe serializzato come:

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

Questo valore, una volta ricevuto nel livello Javascript, può costruire un oggetto Date:

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

A partire da Javascript 1.8.5, gli oggetti Date hanno un metodo toJSON, che restituisce una stringa in un formato standard. Per serializzare l'oggetto Javascript sopra riportato su JSON, pertanto, il comando sarebbe:

d.toJSON()

Che ti darebbe:

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

Questa stringa, una volta ricevuta in Python, potrebbe essere nuovamente deserializzata in un oggetto datetime:

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

Ciò si traduce nel seguente oggetto datetime, che è lo stesso con cui hai iniziato e quindi corretto:

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

50

Usando json, puoi sottoclassare JSONEncoder e sovrascrivere il metodo default () per fornire i tuoi serializzatori personalizzati:

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)

Quindi, puoi chiamarlo così:

>>> DateTimeJSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'

7
Miglioramento minore: utilizzo obj.isoformat(). Puoi anche usare la dumps()chiamata più comune , che accetta altri argomenti utili (come indent): simplejson.dumps (myobj, cls = JSONEncoder, ...)
rcoup

3
Perché ciò chiamerebbe il metodo genitore di JSONEncoder, non il metodo genitore di DateTimeJSONEncoder. IE, saliresti di due livelli.
Brian Arsuaga,

30

Ecco una soluzione abbastanza completa per codificare e decodificare ricorsivamente oggetti datetime.datetime e datetime.date usando il jsonmodulo libreria standard . Ciò richiede Python> = 2.6 poiché il %fcodice di formato nella stringa di formato datetime.datetime.strptime () è supportato solo da allora. Per il supporto di Python 2.5, elimina %fe rimuovi i microsecondi dalla stringa della data ISO prima di provare a convertirla, ma perderai la precisione dei microsecondi, ovviamente. Per l'interoperabilità con stringhe di date ISO di altre fonti, che possono includere un nome di fuso orario o un offset UTC, potrebbe essere necessario rimuovere alcune parti della stringa di date prima della conversione. Per un parser completo per stringhe di date ISO (e molti altri formati di data) consultare il modulo dateutil di terze parti .

La decodifica funziona solo quando le stringhe di date ISO sono valori in una notazione di oggetto letterale JavaScript o in strutture nidificate all'interno di un oggetto. Le stringhe di date ISO, che sono elementi di un array di livello superiore non verranno decodificate.

Cioè funziona:

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

E anche questo:

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

Ma questo non funziona come previsto:

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

Ecco il codice:

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

Se stampi la data come datetime.datetime.utcnow().isoformat()[:-3]+"Z"sarà esattamente come quella prodotta da JSON.stringify () in javascript
w00t

24

Se sei sicuro che solo Javascript utilizzerà il JSON, preferisco passare Datedirettamente gli oggetti Javascript .

Il ctime()metodo sugli datetimeoggetti restituirà una stringa che l'oggetto Javascript Date può comprendere.

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

Javascript lo utilizzerà felicemente come un oggetto letterale e hai incorporato l'oggetto Date.


12
Tecnicamente non valido JSON, ma è un oggetto JavaScript valido letterale. (Per motivi di principio vorrei impostare il Content-Type a text / javascript invece di application / json). Se il consumatore sarà sempre e per sempre essere solo un'implementazione JavaScript, allora sì, questo è abbastanza elegante. Lo userei.
sistema PAUSA

13
.ctime()è un modo MOLTO cattivo per passare le informazioni sul tempo, .isoformat()è molto meglio. Ciò che .ctime()fa è buttare via il fuso orario e l'ora legale come se non esistessero. Quella funzione dovrebbe essere uccisa.
Evgeny

Anni dopo: per favore, non prendere in considerazione l'idea di farlo.
Funzionerà

11

Verso la fine del gioco ... :)

Una soluzione molto semplice è patchare l'impostazione predefinita del modulo json. Per esempio:

import json
import datetime

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

Ora puoi usare json.dumps () come se avesse sempre supportato datetime ...

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

Ciò ha senso se si richiede che questa estensione al modulo json si avvii sempre e si desideri non cambiare il modo in cui l'utente o gli altri utilizzano la serializzazione json (nel codice esistente o meno).

Si noti che alcuni potrebbero considerare l'applicazione di patch alle librerie in quel modo come cattiva pratica. È necessario prestare particolare attenzione nel caso in cui si desideri estendere l'applicazione in più di un modo: in tal caso, suggerisco di utilizzare la soluzione di ramen o JT e scegliere l'estensione json appropriata in ciascun caso.


6
Questo mangia silenziosamente oggetti non serializzabili e li trasforma in None. È possibile che si desideri generare un'eccezione.
Blender,

6

Non c'è molto da aggiungere alla risposta wiki della community, ad eccezione del timestamp !

Javascript utilizza il seguente formato:

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

Lato Python (per il json.dumpsgestore, vedi le altre risposte):

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

Se si lascia fuori quella Z, i framework frontend come angular non possono visualizzare la data nel fuso orario locale del browser:

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

4

Sul lato pitone:

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

Sul lato javascript:

var your_date = new Date(data)

dove i dati sono il risultato di Python



0

Apparentemente il formato della data "giusto" JSON (bene JavaScript) è 23/04/2012 T18: 25: 43.511Z - UTC e "Z". Senza questo JavaScript verrà utilizzato il fuso orario locale del browser Web durante la creazione di un oggetto Date () dalla stringa.

Per un tempo "ingenuo" (ciò che Python chiama un orario senza fuso orario e questo presuppone che sia locale) il seguente forzerà il fuso orario locale in modo che possa essere correttamente convertito in 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)

Perche'e'cosi difficile.


0

Per la conversione della data da Python a JavaScript, l'oggetto data deve essere in formato ISO specifico, ovvero formato ISO o numero UNIX. Se nel formato ISO mancano alcune informazioni, puoi prima convertirlo nel numero Unix con Date.parse. Inoltre, Date.parse funziona anche con React mentre la nuova Data potrebbe attivare un'eccezione.

Nel caso in cui si disponga di un oggetto DateTime senza millisecondi, è necessario considerare quanto segue. :

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

La data di esempio potrebbe ugualmente essere una variabile nell'oggetto result.data dopo una chiamata API.

Per le opzioni per visualizzare la data nel formato desiderato (ad es. Per visualizzare i giorni feriali lunghi), consultare il documento MDN .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.