¿Cómo convertir a un objeto datetime de Python con JSON.loads?

Tengo una representación de cadena de un objeto JSON.

dumped_dict = '{"debug": false, "created_at": "2020-08-09T11:24:20"}' 

Cuando llamo a json.loads con este objeto;

 json.loads(dumped_dict) 

Yo obtengo;

 {'created_at': '2020-08-09T11:24:20', 'debug': False} 

No hay nada malo aquí. Sin embargo, quiero saber si hay una manera de convertir el objeto anterior con json.loads a algo como esto:

 {'created_at': datetime.datetime(2020, 08, 09, 11, 24, 20), 'debug': False} 

En breve, ¿podemos convertir cadenas de fecha y hora a objetos datetime.datetime cuando llamamos a json.loads?

Mi solución hasta ahora:

 >>> json_string = '{"last_updated": {"$gte": "Thu, 1 Mar 2012 10:00:49 UTC"}}' >>> dct = json.loads(json_string, object_hook=datetime_parser) >>> dct {u'last_updated': {u'$gte': datetime.datetime(2012, 3, 1, 10, 0, 49)}} def datetime_parser(dct): for k, v in dct.items(): if isinstance(v, basestring) and re.search("\ UTC", v): try: dct[k] = datetime.datetime.strptime(v, DATE_FORMAT) except: pass return dct 

Para mayor referencia sobre el uso de object_hook: codificador y decodificador JSON

En mi caso, la cadena json proviene de una solicitud GET a mi API REST. Esta solución me permite ‘obtener la fecha correcta’ de forma transparente, sin forzar a los clientes y usuarios a usar prefijos de __date__ como __date__ en el JSON, siempre que la cadena de entrada se ajuste a DATE_FORMAT, que es:

 DATE_FORMAT = '%a, %d %b %Y %H:%M:%S UTC' 

El patrón regex probablemente debería ser refinado aún más

PD: en caso de que se lo pregunte, json_string es una consulta de MongoDB / PyMongo.

Necesitas pasar un object_hook . De la documentación :

object_hook es una función opcional que se llamará con el resultado de cualquier objeto decodificado literalmente (un dict). El valor de retorno de object_hook se utilizará en lugar del dict.

Me gusta esto:

 import datetime import json def date_hook(json_dict): for (key, value) in json_dict.items(): try: json_dict[key] = datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S") except: pass return json_dict dumped_dict = '{"debug": false, "created_at": "2020-08-09T11:24:20"}' loaded_dict = json.loads(dumped_dict, object_hook=date_hook) 

Si también quiere manejar zonas horarias, tendrá que usar dateutil en lugar de strptime.

De la forma en que se formula su pregunta, no hay ninguna indicación de que la cadena sea un valor de fecha. Esto es diferente a la documentación de json que tiene la cadena de ejemplo:

 '{"__complex__": true, "real": 1, "imag": 2}' 

Esta cadena tiene un indicador "__complex__": true que se puede usar para inferir el tipo de datos, pero a menos que exista tal indicador, una cadena es solo una cadena, y todo lo que puede hacer es "__complex__": true una "__complex__": true través de todas las cadenas y decidir si se parecen a las fechas.

En su caso, definitivamente debería usar un esquema si hay uno disponible para su formato.

Haría lo mismo que Nicola sugirió con 2 cambios:

  1. Use dateutil.parser lugar de datetime.datetime.strptime
  2. Definir explícitamente qué excepciones quiero capturar. Generalmente recomiendo evitar a toda costa tener un vacío except:

O en el código:

 import dateutil.parser def datetime_parser(json_dict): for (key, value) in json_dict.items(): try: json_dict[key] = dateutil.parser.parse(value) except (ValueError, AttributeError): pass return json_dict str = "{...}" # Some JSON with date obj = json.loads(str, object_hook=datetime_parser) print(obj) 

Puede usar expresiones regulares para determinar si desea o no convertir un campo determinado a fecha y hora como:

 def date_hook(json_dict): for (key, value) in json_dict.items(): if type(value) is str and re.match('^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d*$', value): json_dict[key] = datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S.%f") elif type(value) is str and re.match('^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$', value): json_dict[key] = datetime.datetime.strptime(value, "%Y-%m-%dT%H:%M:%S") else: pass return json_dict 

Luego puede hacer referencia a la función date_hook usando el parámetro object_hook en su llamada a json.loads ():

 json_data = '{"token": "faUIO/389KLDLA", "created_at": "2016-09-15T09:54:20.564"}' data_dictionary = json.loads(json_data, object_hook=date_hook) 

Por lo que sé, no hay una solución inmediata para esto.

En primer lugar, la solución debe tener en cuenta el esquema json para distinguir correctamente entre cadenas y tiempos de datos. Hasta cierto punto, puedes adivinar el esquema con el inferencer de esquema de json (google para el inferencer de esquema de json github) y luego corregir los lugares que son realmente fechas de datos.

Si se conoce el esquema, debería ser bastante fácil hacer una función, que analice json y sustituya las representaciones de cadena con datetime. Tal vez se pueda encontrar alguna inspiración para el código en el producto de validación (y la validación del esquema json también podría ser una buena idea).

Inspirado por la respuesta de Nicola y adaptado a python3 (str en lugar de basestring):

 import re from datetime import datetime datetime_format = "%Y-%m-%dT%H:%M:%S" datetime_format_regex = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$') def datetime_parser(dct): for k, v in dct.items(): if isinstance(v, str) and datetime_format_regex.match(v): dct[k] = datetime.strptime(v, datetime_format) return dct 

Esto evita el uso de un mecanismo try / except. En el código de prueba de OP:

 >>> import json >>> json_string = '{"debug": false, "created_at": "2020-08-09T11:24:20"}' >>> json.loads(json_string, object_hook=datetime_parser) {'created_at': datetime.datetime(2020, 8, 9, 11, 24, 20), 'debug': False} 

Las variables regex y datetime_format pueden adaptarse fácilmente para adaptarse a otros patrones, por ejemplo, sin la T en el medio.

Para convertir una cadena guardada en isoformato (por lo tanto, almacenada con microsegundos) en un objeto de fecha y hora, consulte esta pregunta .