Подтвердить что ты не робот

Сериализация члена 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

Как я могу избежать этого?

4b9b3361

Ответ 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)