Data / hora JSON entre Python e JavaScript

393

Desejo enviar um objeto datetime.datetime no formato serializado do Python usando JSON e desserializar no JavaScript usando JSON. Qual é a melhor maneira de fazer isso?

Peter Mortensen
fonte
Você prefere usar uma biblioteca ou deseja codificar isso sozinho?
guettli

Respostas:

370

Você pode adicionar o parâmetro 'default' ao json.dumps para lidar com isso:

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

Qual é o formato ISO 8601 .

Uma função de manipulador padrão mais abrangente:

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

Atualização: saída adicionada do tipo e do valor.
Atualização: também lida com a data

JT.
fonte
11
O problema é que, se você tiver outros objetos na lista / dict, esse código os converterá em Nenhum.
Tomasz Wysocki
5
O json.dumps também não saberá como convertê-los, mas a exceção está sendo suprimida. Infelizmente, uma correção lambda de uma linha tem suas deficiências. Se você preferir ter uma exceção levantada sobre as incógnitas (o que é uma boa ideia), use a função que adicionei acima.
JT.
9
o formato de saída completo também deve ter fuso horário ... e isoformat () não fornece essa funcionalidade ... portanto, certifique-se de anexar essas informações na string antes de retornar
Nick Franceschina
3
Este é o melhor caminho a percorrer. Por que isso não foi selecionado como resposta?
Brendon Crawford
16
O lambda pode ser adaptado para chamar a implementação base na não-datetime tipos, então TypeError pode ser aumentado se necessário:dthandler = lambda obj: obj.isoformat() if isinstance(obj, datetime) else json.JSONEncoder().default(obj)
Pascal Bourque
81

Para projetos em vários idiomas, descobri que as seqüências contendo datas RfC 3339 são o melhor caminho a percorrer. Uma data da RfC 3339 é assim:

  1985-04-12T23:20:50.52Z

Eu acho que a maior parte do formato é óbvia. A única coisa um tanto incomum pode ser o "Z" no final. Significa GMT / UTC. Você também pode adicionar um deslocamento de fuso horário como +02: 00 para CEST (Alemanha no verão). Pessoalmente, prefiro manter tudo no UTC até que seja exibido.

Para exibição, comparações e armazenamento, você pode deixá-lo no formato de sequência em todos os idiomas. Se você precisar da data para cálculos, é fácil convertê-la novamente em um objeto de data nativo na maioria dos idiomas.

Portanto, gere o JSON assim:

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

Infelizmente, o construtor Date do Javascript não aceita seqüências de caracteres RfC 3339, mas existem muitos analisadores disponíveis na Internet.

O huTools.hujson tenta lidar com os problemas de codificação mais comuns que você pode encontrar no código Python, incluindo objetos de data / data e hora enquanto manipula fusos horários corretamente.

max
fonte
17
Esse mecanismo de formatação de data é suportado nativamente, tanto por datetime: datetime.isoformat () quanto por simplejson, que irá despejar datetimeobjetos como isoformatseqüências de caracteres por padrão. Não há necessidade de strftimehackers manuais .
jrk
9
@ jrk - Eu não estou recebendo conversão automática de datetimeobjetos para a isoformatseqüência de caracteres. Para mim, simplejson.dumps(datetime.now())os rendimentosTypeError: datetime.datetime(...) is not JSON serializable
kostmo
6
json.dumps(datetime.datetime.now().isoformat())é onde a mágica acontece.
usar o seguinte código
2
A beleza do simplejson é que, se eu tiver uma estrutura de dados complexa, ela será analisada e transformada em JSON. Se eu tiver que executar json.dumps (datetime.datetime.now (). Isoformat ()) para cada objeto de data e hora, eu perco isso. Existe uma maneira de corrigir isso?
precisa saber é o seguinte
11
superjoe30: consulte stackoverflow.com/questions/455580/… sobre como fazer isso
max
67

Eu resolvi isso.

Digamos que você tenha um objeto datetime do Python, d , criado com datetime.now (). Seu valor é:

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

Você pode serializá-lo para JSON como uma sequência de data e hora ISO 8601:

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

O exemplo de objeto datetime seria serializado como:

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

Esse valor, uma vez recebido na camada Javascript, pode construir um objeto Date:

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

A partir do Javascript 1.8.5, os objetos Date têm um método toJSON, que retorna uma string em um formato padrão. Para serializar o objeto Javascript acima de volta para JSON, portanto, o comando seria:

d.toJSON()

O que lhe daria:

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

Essa sequência, uma vez recebida em Python, pode ser desserializada de volta para um objeto datetime:

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

Isso resulta no seguinte objeto datetime, que é o mesmo com o qual você iniciou e, portanto, correto:

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

Usando json, você pode subclassificar JSONEncoder e substituir o método default () para fornecer seus próprios serializadores personalizados:

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)

Então, você pode chamar assim:

>>> DateTimeJSONEncoder().encode([datetime.datetime.now()])
'["2010-06-15T14:42:28"]'
ramen
fonte
7
Melhoria menor - use obj.isoformat(). Você também pode usar o mais comum dumps()chamada, que tem outros argumentos úteis (como indent): simplejson.dumps (myObj, cls = JSONEncoder, ...)
rcoup
3
Como isso chamaria o método pai de JSONEncoder, não o método pai de DateTimeJSONEncoder. IE, você estaria subindo dois níveis.
Brian Arsuaga /
30

Aqui está uma solução bastante completa para codificar e decodificar recursivamente os objetos datetime.datetime e datetime.date usando o jsonmódulo de biblioteca padrão . Isso precisa do Python> = 2.6, pois o %fcódigo do formato datetime.datetime.strptime () é suportado somente desde então. Para suporte ao Python 2.5, solte %fe retire os microssegundos da sequência de datas ISO antes de tentar convertê-lo, mas você perderá a precisão dos microssegundos, é claro. Para interoperabilidade com seqüências de datas ISO de outras fontes, que podem incluir um nome de fuso horário ou deslocamento UTC, também é necessário remover algumas partes da sequência de datas antes da conversão. Para um analisador completo de seqüências de datas ISO (e muitos outros formatos de data), consulte o módulo dateutil de terceiros .

A decodificação funciona apenas quando as cadeias de datas ISO são valores em uma notação literal de objeto JavaScript ou em estruturas aninhadas em um objeto. As sequências de datas ISO, que são itens de uma matriz de nível superior, não serão decodificadas.

Ou seja, isso funciona:

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 isso também:

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

Mas isso não funciona conforme o esperado:

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

Aqui está o código:

__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
fonte
Se você imprimir a data como datetime.datetime.utcnow().isoformat()[:-3]+"Z"se fosse exatamente o que JSON.stringify () produz em javascript
w00t
24

Se você tiver certeza de que apenas o Javascript consumirá o JSON, prefiro transmitir Dateobjetos Javascript diretamente.

O ctime()método nos datetimeobjetos retornará uma sequência que o objeto Data Javascript possa entender.

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

O Javascript o utilizará como um literal de objeto, e você terá seu objeto Date incorporado.

Tríptico
fonte
12
Tecnicamente, JSON não é válido, mas é um literal de objeto JavaScript válido. (Por uma questão de princípio que iria definir o Content-Type para text / javascript em vez de application / json.) Se o consumidor sempre e para sempre ser unicamente uma implementação JavaScript, então sim, isso é muito elegante. Eu usaria isso.
system PAUSE
13
.ctime()é uma maneira MUITO ruim de passar informações de tempo, .isoformat()é muito melhor. O que .ctime()faz é jogar fora o fuso horário e o horário de verão como se eles não existissem. Essa função deve ser eliminada.
Evgeny 01/01
Anos depois: por favor, nunca considere fazer isso. Isso só vai funcionar se você eval () seu JSON em Javascript que você realmente não deve ...
domenukk
11

Tarde no jogo ... :)

Uma solução muito simples é corrigir o padrão do módulo json. Por exemplo:

import json
import datetime

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

Agora, você pode usar json.dumps () como se ele sempre tivesse suporte em data e hora ...

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

Isso faz sentido se você precisar que essa extensão do módulo json sempre entre em ação e deseje não alterar a maneira como você ou outras pessoas usam a serialização json (no código existente ou não).

Observe que alguns podem considerar o patch de bibliotecas dessa maneira como uma má prática. Cuidados especiais devem ser tomados caso você queira estender seu aplicativo de mais de uma maneira - nesse caso, sugiro usar a solução por ramen ou JT e escolher a extensão json apropriada em cada caso.

davidhadas
fonte
6
Isso silenciosamente come objetos não serializáveis ​​e os transforma em None. Você pode lançar uma exceção.
Blender
6

Não há muito a acrescentar à resposta do wiki da comunidade, exceto o carimbo de data e hora !

Javascript usa o seguinte formato:

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

Lado do Python (para o json.dumpsmanipulador, veja as outras respostas):

>>> 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 você deixar esse Z de fora, estruturas de front-end como angular não poderão exibir a data no fuso horário local do navegador:

> $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
fonte
4

No lado do 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

No lado do javascript:

var your_date = new Date(data)

onde os dados são resultado do python

Sank
fonte
4

Meu conselho é usar uma biblioteca. Existem vários disponíveis em pypi.org.

Eu uso este aqui, ele funciona bem: https://pypi.python.org/pypi/asjson

guettli
fonte
0

Aparentemente, o formato de data JSON "correto" (bem JavaScript) é 2012-04-23T18: 25: 43.511Z - UTC e "Z". Sem esse JavaScript, o fuso horário do navegador da Web será usado ao criar um objeto Date () a partir da string.

Por um horário "ingênuo" (o que o Python chama de horário sem fuso horário e isso pressupõe que é local), o abaixo forçará o fuso horário local para que possa ser convertido corretamente no 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)

Por que isso é tão difícil.

cagney
fonte
0

Para a conversão de data do Python para JavaScript, o objeto de data precisa estar no formato ISO específico, ou seja, no formato ISO ou no número UNIX. Se o formato ISO não tiver algumas informações, você poderá converter para o número Unix com Date.parse primeiro. Além disso, o Date.parse também funciona com o React, enquanto o novo Date pode acionar uma exceção.

Caso você tenha um objeto DateTime sem milissegundos, é necessário considerar o seguinte. :

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

A data do exemplo pode ser igualmente uma variável no objeto result.data após uma chamada de API.

Para opções para exibir a data no formato desejado (por exemplo, para exibir dias úteis prolongados), consulte o documento MDN .

Patrick
fonte