¿Codificador JSON de Python para soportar datetime?

¿Existe alguna forma elegante de hacer que el codificador Python JSON sea compatible con datetime? ¿Algún módulo de terceros o hackeo fácil?

Estoy usando la envoltura de la base de datos del tornado para obtener algunos raws de db para generar un json. El resultado de la consulta incluye una columna de marca de tiempo MySQL regular.

Es bastante molesto que el codificador json predeterminado de Python no sea compatible con 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 practica? ¡Muchas gracias!

ps: encontré un truco sucio al modificar el método predeterminado del codificador JSON de Python:

Cambio:

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

A:

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

Bueno, será una solución temporal solo para el entorno de desarrollo.

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

¿Hay alguna manera mejor? No quiero modificar el código Python, 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 solo paso.

¡Muchas gracias!

Los documentos sugieren subclasificar JSONEncoder e implementar su propio método predeterminado. Parece que básicamente estás ahí, y no es un “truco sucio”.

La razón por la que las fechas no son manejadas por el codificador predeterminado es que no hay una representación estándar de una fecha en JSON. Algunas personas 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) 

json.dumps(thing, default=str)

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) 

Resultado:

 {"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:

  • documentacion json
  • Mark Hildreth – Subclasificando JSONEncoder y JSONDecoder
  • Cédric Krier – trytond.protocols.jsonrpc código fuente

Nota: Se puede hacer más flexible al pasar un diccionario personalizado con tipos como claves y argumentos, kwargs como valores al __init__() del codificador y usar eso (o un diccionario predeterminado) en el método default() .

 json.dumps(r, default=lambda o: o.isoformat() if hasattr(o, 'isoformat') else o) 

El proyecto Tryton tiene una implementación JSONEncoder para objetos datetime.datetime , datetime.date y datetime.time (con otros). Se utiliza para la comunicación JSON RPC entre el servidor y el cliente.

Consulte http://hg.tryton.org/2.4/trytond/file/ade5432ac476/trytond/protocols/jsonrpc.py#l53

Convierta el tipo de fecha y hora en una marca de tiempo de Unix, luego codifique el contenido en un json.

por ejemplo: http://codepad.org/k3qF09Kr

Solo crea un codificador personalizado

(la pequeña pero importante adición a la respuesta de Cole es el manejo de pd.NaT (o valores de marca de tiempo nulos / vacíos), ya que sin la adición obtendrá conversiones de marca de tiempo muy extrañas para NaT / datos de marca de tiempo faltantes)

 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 utilízalo para codificar un dataframe:

 df_as_dict = df.to_dict(outtype = 'records') # transform to dict df_as_json = CustomEncoder().encode(df_as_dict) #transform to json 

Dado que el codificador estandarizó los datos, el decodificador normal actuará bien para transformarlo de nuevo en un dataframe:

 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 coloca el dataframe en un dictado más grande antes de la encoding, 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)