2012-01-09 30 views
21

Tengo una representación de cadena de un objeto JSON.Cómo convertir a un objeto de fecha y hora de Python con JSON.loads?

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

Cuando llamo json.loads con este objeto;

json.loads(dumped_dict) 

I get;

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

No hay nada de malo aquí. Sin embargo, me gustaría 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} 

Poco, somos capaces de convertir las cadenas de fecha y hora a los objetos reales, mientras que datetime.datetime llamada json.loads?

Respuesta

17

Mi solución hasta el momento:

>>> 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: JSON encoder and decoder

En mi caso, la cadena JSON está viniendo 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 en prefijos hardcoding como __date__ en el JSON, siempre y cuando la cadena de entrada se ajusta a DATE_FORMAT que es:

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

El patrón de expresión debe probablemente sea más refinado

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

+0

Proporcione algunos comentarios/sugerencias que no sean un simple -1, para que pueda aprender algo al menos :) –

+0

Me salvó absolutamente. – David

+0

@NicolaIarocci parece una solución increíble, sin embargo, ¿no está esto también forzando a los clientes a codificar un sufijo "UTC" en su JSON? –

1

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

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

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

3

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 que 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 puede utilizarse para inferir el tipo de los datos, pero a menos que exista tal indicador, una cadena es solo una cadena, y todo lo que puede hacer es regexp su camino a través de todas las cadenas y decidir si se parecen a las fechas.

En su caso, definitivamente debe usar un esquema si hay uno disponible para su formato.

+0

¿Qué documentación exactamente de json propone utilizar nombres de doble subrayado? He visto \ _ \ _ tipo, por ejemplo, pero todos parecen convenciones de uso limitado. –

+0

El ejemplo fue tomado de la documentación del paquete 'json'. –

14

Debe pasar object_hook.Desde el documentation:

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

De esta manera:

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 las zonas horarias que tendrá que utilizar en lugar de dateutil strptime.

+1

Usar try/catch como estructura de control no es ideal. – Maciej

1

Se puede usar expresiones regulares para determinar si o no desea convertir un determinado campo de fecha y hora de este modo:

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 

A continuación, puede hacer referencia a la función date_hook utilizando 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) 
3

yo haría lo mismo que Nicola sugerido con 2 cambios:

  1. Use dateutil.parser en lugar de datetime.datetime.strptime
  2. Defina explícitamente qué excepciones quiero capturar. En general, 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) 
+0

Dirección interesante para probar. Pero parece un poco lento para ejecutar un análisis de fecha y hora en cada elemento en el JSON. La mayoría de los artículos no van a ser valores de fecha y hora. – swdev

0

Inspirado por Nicola de answer y adaptado para python3 (str en lugar de la cadena base):

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 usar un mecanismo try/except. En 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} 

la expresión regular y datetime_format variables pueden ser fácilmente adaptados para ajustarse a los patrones de otros, por ejemplo, sin la T en el medio.

Para convertir una cadena guardada en isoformato (por lo tanto, almacenada con microsegundos) a un objeto datetime, consulte this question.

Cuestiones relacionadas