Сериализация члена Enum для JSON
Как сериализовать член Python Enum
в JSON, чтобы я мог десериализовать полученный JSON в объект Python?
Например, этот код:
from enum import Enum
import json
class Status(Enum):
success = 0
json.dumps(Status.success)
приводит к ошибке:
TypeError: <Status.success: 0> is not JSON serializable
Как я могу избежать этого?
Ответы
Ответ 1
Если вы хотите закодировать произвольный член enum.Enum
на JSON и затем декодировать
он как тот же член перечисления (а не просто атрибут enum member value
), вы можете сделать это, написав собственный JSONEncoder
класс, и функцию декодирования, которая передается в качестве аргумента object_hook
в json.load()
или json.loads()
:
PUBLIC_ENUMS = {
'Status': Status,
# ...
}
class EnumEncoder(json.JSONEncoder):
def default(self, obj):
if type(obj) in PUBLIC_ENUMS.values():
return {"__enum__": str(obj)}
return json.JSONEncoder.default(self, obj)
def as_enum(d):
if "__enum__" in d:
name, member = d["__enum__"].split(".")
return getattr(PUBLIC_ENUMS[name], member)
else:
return d
Функция as_enum
основана на том, что JSON был закодирован с помощью EnumEncoder
или того, что ведет себя одинаково с ним.
Ограничение членов PUBLIC_ENUMS
необходимо, чтобы избежать использования злонамеренно созданного текста, например, для вызова кода вызова для сохранения частной информации (например, секретного ключа, используемого приложением), в не связанное с ним поле базы данных, из где он может быть выставлен (см. http://chat.stackoverflow.com/transcript/message/35999686#35999686).
Пример использования:
>>> data = {
... "action": "frobnicate",
... "status": Status.success
... }
>>> text = json.dumps(data, cls=EnumEncoder)
>>> text
'{"status": {"__enum__": "Status.success"}, "action": "frobnicate"}'
>>> json.loads(text, object_hook=as_enum)
{'status': <Status.success: 0>, 'action': 'frobnicate'}
Ответ 2
Правильный ответ зависит от того, что вы собираетесь делать с сериализованной версией.
Если вы собираетесь несериализоваться на Python, см. Нулевой ответ.
Если ваша сериализованная версия перейдет на другой язык, вы, вероятно, захотите вместо этого использовать IntEnum
, который автоматически сериализуется как соответствующее целое число:
from enum import IntEnum
import json
class Status(IntEnum):
success = 0
failure = 1
json.dumps(Status.success)
и это возвращает:
'0'
Ответ 3
Я знаю, что это старо, но я чувствую, что это поможет людям. Я только что прошел эту проблему и обнаружил, что если вы используете строковые перечисления, объявление ваших перечислений как подкласса str
хорошо работает почти во всех ситуациях:
import json
from enum import Enum
class LogLevel(str, Enum):
DEBUG = 'DEBUG'
INFO = 'INFO'
print(LogLevel.DEBUG)
print(json.dumps(LogLevel.DEBUG))
print(json.loads('"DEBUG"'))
print(LogLevel('DEBUG'))
Будет выводить:
LogLevel.DEBUG
"DEBUG"
DEBUG
LogLevel.DEBUG
Как видите, при загрузке JSON выводится строка DEBUG
но она легко возвращается в объект LogLevel. Хороший вариант, если вы не хотите создавать пользовательский JSONEncoder.
Ответ 4
Если вы хотите сериализовать строковое перечисление, ваш лучший вариант, вероятно, это, однако, другой подход, который работал для меня, был таким, который был похоронен в комментариях для другого ответа:
from enum import EnumMeta
import json
class Status(EnumMeta):
success = "win"
failure = "lose"
json.dumps(Status.success)
возвращает:
'win'
Предупреждение: если вы это сделаете, вы потеряете способность перебирать перечисление.
[x for x in Status] # TypeError
Ответ 5
Мне понравился ответ Zero Piraeus, но немного изменил его для работы с API для веб-служб Amazon (AWS), известных как Boto.
class EnumEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, Enum):
return obj.name
return json.JSONEncoder.default(self, obj)
Затем я добавил этот метод к моей модели данных:
def ToJson(self) -> str:
return json.dumps(self.__dict__, cls=EnumEncoder, indent=1, sort_keys=True)
Я надеюсь, что это поможет кому-то.
Ответ 6
Если вы используете jsonpickle
самый простой способ должен выглядеть так, как jsonpickle
ниже.
from enum import Enum
import jsonpickle
@jsonpickle.handlers.register(Enum, base=True)
class EnumHandler(jsonpickle.handlers.BaseHandler):
def flatten(self, obj, data):
return obj.value # Convert to json friendly format
if __name__ == '__main__':
class Status(Enum):
success = 0
error = 1
class SimpleClass:
pass
simple_class = SimpleClass()
simple_class.status = Status.success
json = jsonpickle.encode(simple_class, unpicklable=False)
print(json)
После сериализации Json вы получите, как и ожидалось, {"status": 0}
вместо
{"status": {"__objclass__": {"py/type": "__main__.Status"}, "_name_": "success", "_value_": 0}}
Ответ 7
В Python 3.7 можно просто использовать json.dumps(enum_obj, default=str)