Как преобразовать объект Python datetime с помощью JSON.loads?
У меня есть строковое представление объекта JSON.
dumped_dict = '{"debug": false, "created_at": "2020-08-09T11:24:20"}'
Когда я вызываю json.loads с этим объектом,
json.loads(dumped_dict)
Я получаю:
{'created_at': '2020-08-09T11:24:20', 'debug': False}
Здесь нет ничего плохого. Тем не менее, я хочу знать, есть ли способ конвертировать вышеуказанный объект с json.loads в нечто вроде этого:
{'created_at': datetime.datetime(2020, 08, 09, 11, 24, 20), 'debug': False}
В скором времени мы можем преобразовать строки datetime в фактические объекты datetime.datetime, в то время как
вызов json.loads?
Ответы
Ответ 1
Мое решение:
>>> 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
Дополнительная информация об использовании object_hook: Кодер и декодер JSON
В моем случае строка json поступает из запроса GET в мой REST API. Это решение позволяет прозрачно "получать дату правильно", не заставляя клиентов и пользователей префиксов жесткого кодирования, таких как __date__
, в JSON, если входная строка соответствует DATE_FORMAT, которая:
DATE_FORMAT = '%a, %d %b %Y %H:%M:%S UTC'
Шаблон регулярного выражения, вероятно, должен быть дополнительно уточнен
PS: в случае, если вам интересно, json_string - это запрос MongoDB/PyMongo.
Ответ 2
Вам нужно передать object_hook. Из документа :
object_hook - это необязательная функция, которая будет вызываться с помощью результат любого объектного литерала декодированного (a dict). Возвращаемое значение object_hook будет использоваться вместо dict.
Вот так:
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)
Если вы также хотите обрабатывать часовые пояса, вам придется использовать dateutil вместо strptime.
Ответ 3
Я бы сделал то же самое, что Nicola предложил с двумя изменениями:
- Используйте
dateutil.parser
вместо datetime.datetime.strptime
- Определить, какие исключения я хочу поймать. Я вообще рекомендую избегать любой ценой пустого
except:
Или в коде:
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)
Ответ 4
Способ, которым задан ваш вопрос, не указывает на json, что строка является значением даты. Это отличается от документации json, которая имеет пример строки:
'{"__complex__": true, "real": 1, "imag": 2}'
В этой строке есть индикатор "__complex__": true
, который может использоваться для вывода типа данных, но если нет такого индикатора, строка является просто строкой, и все, что вы можете сделать, - это регулярное перемещение по всем строки и решить, выглядят ли они как даты.
В вашем случае вы обязательно должны использовать схему, если она доступна для вашего формата.
Ответ 5
Вы можете использовать регулярное выражение, чтобы определить, хотите ли вы преобразовать определенное поле в datetime следующим образом:
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
Затем вы можете ссылаться на функцию date_hook, используя параметр object_hook в своем вызове 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)
Ответ 6
Насколько я знаю, для этого нет готового решения.
Прежде всего, решение должно учитывать json schema, чтобы правильно различать строки и даты. В какой-то степени вы можете догадаться о схеме с помощью json schema inferencer (google для json schema inferencer github), а затем исправить места, которые действительно являются datetime.
Если схема известна, должно быть довольно легко сделать функцию, которая анализирует json и заменяет представления строк с помощью datetime. Некоторое вдохновение для кода можно было бы найти из validictory продукта (и проверка схемы json также может быть хорошей идеей).
Ответ 7
Вдохновленный Nicola ответ и адаптированный к python3 (str вместо 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
Это позволяет избежать использования механизма try/except.
В тестовом коде 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}
Переменные регулярных выражений и datetime_format
могут быть легко адаптированы для соответствия другим шаблонам, например. без T в середине.
Чтобы преобразовать строку, сохраненную в isoformat (поэтому сохраненную с микросекундами), обратно к объекту datetime, обратитесь к этому вопросу.
Ответ 8
Метод реализует рекурсивный поиск строк в формате даты и времени
import json
from dateutil.parser import parse
def datetime_parser(value):
if isinstance(value, dict):
for k, v in value.items():
value[k] = datetime_parser(v)
elif isinstance(value, list):
for index, row in enumerate(value):
value[index] = datetime_parser(row)
elif isinstance(value, str) and value:
try:
value = parse(value)
except (ValueError, AttributeError):
pass
return value
json_to_dict = json.loads(YOUR_JSON_STRING, object_hook=datetime_parser)