2012-08-25 14 views
11

¿Hay alguna manera elegante de hacer que Python JSON encoder sea compatible con datetime? algún módulo de terceros o hack fácil?Python, codificador JSON para admitir datetime?

Estoy usando la envoltura de la base de datos de tornado para buscar algunas raws de db para generar un json. El resultado de la consulta incluye una columna regular de timestamp de MySQL.

Es bastante molesto que el codificador json predeterminado de Python no admita su propio tipo de fecha y hora, que es tan común en todo tipo de consultas de bases de datos.

No quiero modificar el propio codificador json de Python. alguna buena práctica? ¡Muchas gracias!

PS: He encontrado un truco sucio modificando el método por defecto codificador Python JSON:

Cambio:

def default(self, o): 
    raise TypeError(repr(o) + " is not JSON serializable") 

Para:

def default(self, o): 
    from datetime import date 
    from datetime import datetime 
    if isinstance(o, datetime): 
     return o.isoformat() 
    elif isinstance(o, date): 
     return o.isoformat() 
    else: 
     raise TypeError(repr(o) + " is not JSON serializable") 

así, será una solución temporal solo para el entorno dev.

Pero para una solución a largo plazo o un entorno de producción, esto es bastante feo, y tengo que hacer la modificación cada vez que despliegue en un nuevo servidor.

¿Hay una manera mejor? No quiero modificar el código de Python en sí, ni el código fuente de Tornado. ¿Hay algo que pueda hacer con mi propio código de proyecto para que esto suceda? preferiblemente en un ritmo.

¡Muchas gracias!

+1

Ver: http://stackoverflow.com/questions/455580/json-datetime-between-python-and-javascript –

+0

el problema con el método de la subclase, es que falla para toda la otros usos de La codificación json, como django simple, "dumpdata" – mcr

Respuesta

17

The docs suggest la subclasificación JSONEncoder e implementar su propio método por defecto. Parece que básicamente estás allí, y no es un "truco sucio".

El codificador predeterminado no maneja las fechas de las razones porque no hay una representación estándar de una fecha en JSON. Some people están usando el formato /Date(1198908717056)/, pero prefiero el formato ISO personalmente.

import datetime 

class DateTimeEncoder(json.JSONEncoder): 
    def default(self, obj): 
     if isinstance(obj, datetime.datetime): 
      return obj.isoformat() 
     elif isinstance(obj, datetime.date): 
      return obj.isoformat() 
     elif isinstance(obj, datetime.timedelta): 
      return (datetime.datetime.min + obj).time().isoformat() 
     else: 
      return super(DateTimeEncoder, self).default(obj) 

DateTimeEncoder().encode(object) 
+0

muchas gracias. este isoformat() lo hace parecer aún mejor. :) – horacex

0

Convierta el tipo de fecha y hora en una marca de tiempo de unix, luego codifique los contenidos en un json.

p. Ej. : http://codepad.org/k3qF09Kr

+0

, significa cambiar el tipo de fecha y hora predeterminado de Python? cómo hacerlo? ¿Será demasiado arriesgado? ¿Romperá las cosas? – horacex

+0

@horacex no, simplemente modifique el conjunto de resultados que proviene de la envoltura de la base de datos de tornado. – DhruvPathak

7

hice mis propias clases para mi proyecto:

import datetime 
import decimal 
import json 
import sys 

class EnhancedJSONEncoder(json.JSONEncoder): 
    def default(self, obj): 
     if isinstance(obj, datetime.datetime): 
      ARGS = ('year', 'month', 'day', 'hour', 'minute', 
        'second', 'microsecond') 
      return {'__type__': 'datetime.datetime', 
        'args': [getattr(obj, a) for a in ARGS]} 
     elif isinstance(obj, datetime.date): 
      ARGS = ('year', 'month', 'day') 
      return {'__type__': 'datetime.date', 
        'args': [getattr(obj, a) for a in ARGS]} 
     elif isinstance(obj, datetime.time): 
      ARGS = ('hour', 'minute', 'second', 'microsecond') 
      return {'__type__': 'datetime.time', 
        'args': [getattr(obj, a) for a in ARGS]} 
     elif isinstance(obj, datetime.timedelta): 
      ARGS = ('days', 'seconds', 'microseconds') 
      return {'__type__': 'datetime.timedelta', 
        'args': [getattr(obj, a) for a in ARGS]} 
     elif isinstance(obj, decimal.Decimal): 
      return {'__type__': 'decimal.Decimal', 
        'args': [str(obj),]} 
     else: 
      return super().default(obj) 


class EnhancedJSONDecoder(json.JSONDecoder): 

    def __init__(self, *args, **kwargs): 
     super().__init__(*args, object_hook=self.object_hook, 
         **kwargs) 

    def object_hook(self, d): 
     if '__type__' not in d: 
      return d 
     o = sys.modules[__name__] 
     for e in d['__type__'].split('.'): 
      o = getattr(o, e) 
     args, kwargs = d.get('args',()), d.get('kwargs', {}) 
     return o(*args, **kwargs) 

if __name__ == '__main__': 
    j1 = json.dumps({'now': datetime.datetime.now(), 
     'val': decimal.Decimal('9.3456789098765434987654567')}, 
     cls=EnhancedJSONEncoder) 
    print(j1) 
    o1 = json.loads(j1, cls=EnhancedJSONDecoder) 
    print(o1) 

Resultados:

{"val": {"args": ["9.3456789098765434987654567"], "__type__": "decimal.Decimal"}, "now": {"args": [2014, 4, 29, 11, 44, 57, 971600], "__type__": "datetime.datetime"}} 
{'val': Decimal('9.3456789098765434987654567'), 'now': datetime.datetime(2014, 4, 29, 11, 44, 57, 971600)} 

Referencias:

Nota: Se puede hacer más flexible pasando un diccionario personalizado con tipos como llaves y args, kwargs como valores al codificador de __init__() y usar eso (o un diccionario por defecto) en el default() método.

3

json.dumps(thing, default=str)

+0

vota por favor si crees que esto cumple con la definición de "hack fácil" mencionada en la pregunta. –

0

Basta con crear un codificador de encargo

(el pequeño, pero importante, además de la respuesta de Cole es el manejo de pd.NaT (o valores timestamp nulo/vacíos), ya que sin la adición obtendrá conversiones de marca de tiempo muy extrañas para NAT/datos de fecha y hora que faltan)

class CustomEncoder(json.JSONEncoder): 
    def default(self, obj): 
     if pd.isnull(obj): 
      return None 
     elif isinstance(obj, datetime): 
      return obj.isoformat() 
     elif isinstance(obj, date): 
      return obj.isoformat() 
     elif isinstance(obj, timedelta): 
      return (datetime.min + obj).time().isoformat() 
     else: 
      return super(CustomEncoder, self).default(obj) 

luego usarlo para codificar una trama de datos:

df_as_dict = df.to_dict(outtype = 'records') # transform to dict 

df_as_json = CustomEncoder().encode(df_as_dict) #transform to json 

Desde el codificador normalizado los datos, el decodificador regular actuar bien en la transformación de nuevo a una trama de datos:

result_as_dict = json.JSONDecoder().decode(df_as_json) # decode back to dict 

result_df = pd.DataFrame(result) # transform dict back to dataframe 

Por supuesto, esto también funcionará si se pone la trama de datos en un diccionario más grande antes de la codificación, por ejemplo

input_dict = {'key_1':val_1,'key_2':val_2,...,'df_as_dict':df_as_dict} 
input_json = CustomEncoder().encode(input_dict) 
input_json_back_as_dict = json.JSONDecoder().decode(input_json) 
input_df_back_as_dict = input_json_back_as_dict['df_as_dict'] 
input_df_back_as_df = pd.DataFrame(input_df_back_as_dict) 
1
json.dumps(r, default=lambda o: o.isoformat() if hasattr(o, 'isoformat') else o) 
+1

Si bien este fragmento de código puede resolver la pregunta, [incluyendo una explicación] (http://meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) realmente ayuda a mejorar la calidad de su publicación. Recuerde que usted está respondiendo la pregunta a los lectores en el futuro, y es posible que esas personas no sepan los motivos de su sugerencia de código. – andreas

Cuestiones relacionadas