#python #json #python-3.x #datetime
#python #json #python-3.x #дата и время
Вопрос:
Пытаюсь сериализовать dict, содержащий объекты datetime в качестве ключей для json. В нескольких других ответах предлагалось использовать json.dump
параметры default
или cls
однако, ни один из этих методов, похоже, даже не вызывается. Смотрите MWES ниже. Чего я не понимаю?
Используя Default
from datetime import datetime
import json
def default(obj):
print("Default Called")
if isinstance(obj, (datetime, date)):
return obj.isoformat()
test = {datetime(1970, 1, 1, 8, 0, 0): 10}
with open("output.json", "w") as fo:
json.dump(test, fo, default=default)
Traceback (most recent call last):
File "test.py", line 21, in <module>
json.dump(test, fo, default=default)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 179, in dump
for chunk in iterable:
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 431, in _iterencode
yield from _iterencode_dict(o, _current_indent_level)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 376, in _iterencode_dict
raise TypeError(f'keys must be str, int, float, bool or None, '
TypeError: keys must be str, int, float, bool or None, not datetime
Используя cls
from datetime import datetime
import json
class DateEncoder(json.JSONEncoder):
def default(self, obj):
print("Default called")
if isinstance(obj, (datetime, date)):
return obj.isoformat()
# Let the base class default method raise the TypeError
return json.JSONEncoder.default(self, obj)
test = {datetime(1970, 1, 1, 8, 0, 0): 10}
with open("output.json", "w") as fo:
json.dump(test, fo, cls=DateEncoder)
Traceback (most recent call last):
File "test.py", line 16, in <module>
json.dump(test, fo, cls=DateEncoder)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 179, in dump
for chunk in iterable:
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 431, in _iterencode
yield from _iterencode_dict(o, _current_indent_level)
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 376, in _iterencode_dict
raise TypeError(f'keys must be str, int, float, bool or None, '
TypeError: keys must be str, int, float, bool or None, not datetime
Комментарии:
1. объекты datetime неизменяемы docs.python.org/3/library/datetime.html#available-types
Ответ №1:
TLDR: .default
вызывается только для dict
значений, а не ключей.
Чтобы уточнить ответ Абхишека Кумара; после просмотра json.JSONEncoder
источника, .default
вызывается только для dict
значений, которые не являются сериализуемыми, никогда ключей. Ключи должны быть одним из str
, int
, float
bool
или None
.
Чтобы использовать datetime
объекты в качестве ключей, .iterencode
необходимо перезаписать, чтобы использовать рекурсивный метод препроцессора для преобразования datetime
в str
:
test = {
"key_1": "Value_1",
"key_2": 10,
"key_3": ["list_" str(i) for i in range(5)],
"key_4": {"nestkey_" str(i) : "nestvalue_" str(i) for i in range(5) },
"key_5": datetime.datetime(1970, 1, 1, 8, 0, 0),
datetime.datetime(1970, 1, 1, 8, 0, 0): "datetime_key"
}
class DateTimeEncoder(json.JSONEncoder):
def _preprocess_date(self, obj):
if isinstance(obj, (datetime.date, datetime.datetime, datetime.timedelta)):
return str(obj)
elif isinstance(obj, dict):
return {self._preprocess_date(k): self._preprocess_date(v) for k,v in obj.items()}
elif isinstance(obj, list):
return [self._preprocess_date(i) for i in obj]
return obj
def default(self, obj):
if isinstance(obj, (datetime.date, datetime.datetime, datetime.timedelta)):
return str(obj)
return super().default(obj)
def iterencode(self, obj):
return super().iterencode(self._preprocess_date(obj))
with open('output.json', 'w') as fo:
json.dump(test, fo, cls=DateTimeEncoder)
Очевидно, что это дорогостоящая операция для больших словарей, поэтому следует соблюдать некоторую осторожность. Кроме того, было бы неплохо json.JSONEncoder
обновиться, чтобы использовать default как для ключей, так и для значений — проблема, созданная по адресу:https://bugs.python.org/issue41569
Комментарии:
1. Кажется, iterencode нужен параметр _one_shot:
def iterencode(self, obj,_one_shot):
Ответ №2:
вы можете попробовать это :
class DatesToStrings(json.JSONEncoder):
def _encode(self, obj):
if isinstance(obj, dict):
def transform_date(o):
return self._encode(o.isoformat() if isinstance(o, datetime) else o)
return {transform_date(k): transform_date(v) for k, v in obj.items()}
else:
return obj
def encode(self, obj):
return super(DatesToStrings, self).encode(self._encode(obj))
with open("output.json", "w") as fo:
json.dump( json.dumps( test, cls=DatesToStrings), fo, cls=DatesToStrings)
Комментарии:
1. Спасибо, Абхишек, но, к сожалению, он все еще возвращается
TypeError: Object of type set is not JSON serializable
2. Вы пробовали это ::
with open("out.json", "w") as fo: json.dump( json.dumps( test, cls=DatesToStrings), fo)
3. Да, я пробовал обе версии. Не могли бы вы рассказать мне, как вы определили
test
пожалуйста?4. я просто скопировал ваш тест :
test = {datetime(1970, 1, 1, 8, 0, 0): 10}
5. Заметил тип на моей стороне. Да, это работает. Спасибо. Есть идеи, почему мой исходный пост не работает?