将Enum成员序列化为JSON

问题:将Enum成员序列化为JSON

如何将PythonEnum成员序列化为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

我该如何避免呢?

How do I serialise a Python Enum member to JSON, so that I can deserialise the resulting JSON back into a Python object?

For example, this code:

from enum import Enum    
import json

class Status(Enum):
    success = 0

json.dumps(Status.success)

results in the error:

TypeError: <Status.success: 0> is not JSON serializable

How can I avoid that?


回答 0

如果您想将任意enum.Enum成员编码为JSON,然后将其解码为相同的enum成员(而不是简单的enum成员的value属性),则可以编写一个自定义JSONEncoder类,并使用一个解码函数作为object_hook参数传递给json.load()or来实现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函数依赖于已使用EnumEncoder或类似行为进行编码的JSON 。

对成员的限制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'}

If you want to encode an arbitrary enum.Enum member to JSON and then decode it as the same enum member (rather than simply the enum member’s value attribute), you can do so by writing a custom JSONEncoder class, and a decoding function to pass as the object_hook argument to json.load() or 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

The as_enum function relies on the JSON having been encoded using EnumEncoder, or something which behaves identically to it.

The restriction to members of PUBLIC_ENUMS is necessary to avoid a maliciously crafted text being used to, for example, trick calling code into saving private information (e.g. a secret key used by the application) to an unrelated database field, from where it could then be exposed (see http://chat.stackoverflow.com/transcript/message/35999686#35999686).

Example usage:

>>> 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'}

回答 1

我知道这很老,但我认为这会对人们有所帮助。我刚刚经历了这个确切的问题,发现您是否使用字符串枚举,将您的枚举声明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,则是一个不错的选择。

I know this is old but I feel this will help people. I just went through this exact problem and discovered if you’re using string enums, declaring your enums as a subclass of str works well for almost all situations:

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'))

Will output:

LogLevel.DEBUG
"DEBUG"
DEBUG
LogLevel.DEBUG

As you can see, loading the JSON outputs the string DEBUG but it is easily castable back into a LogLevel object. A good option if you don’t want to create a custom JSONEncoder.


回答 2

正确答案取决于您打算对序列化版本进行的处理。

如果您要反序列化回Python,请参见Zero的答案

如果您的序列化版本将要使用另一种语言,那么您可能想使用IntEnum代替,它将自动序列化为相应的整数:

from enum import IntEnum
import json

class Status(IntEnum):
    success = 0
    failure = 1

json.dumps(Status.success)

这将返回:

'0'

The correct answer depends on what you intend to do with the serialized version.

If you are going to unserialize back into Python, see Zero’s answer.

If your serialized version is going to another language then you probably want to use an IntEnum instead, which is automatically serialized as the corresponding integer:

from enum import IntEnum
import json

class Status(IntEnum):
    success = 0
    failure = 1

json.dumps(Status.success)

and this returns:

'0'

回答 3

在Python 3.7中,只能使用 json.dumps(enum_obj, default=str)

In Python 3.7, can just use json.dumps(enum_obj, default=str)


回答 4

我喜欢Zero Piraeus的回答,但是对它进行了稍作修改,以便使用称为Boto的Amazon Web Services(AWS)的API。

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)

我希望这可以帮助别人。

I liked Zero Piraeus’ answer, but modified it slightly for working with the API for Amazon Web Services (AWS) known as Boto.

class EnumEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Enum):
            return obj.name
        return json.JSONEncoder.default(self, obj)

I then added this method to my data model:

    def ToJson(self) -> str:
        return json.dumps(self.__dict__, cls=EnumEncoder, indent=1, sort_keys=True)

I hope this helps someone.


回答 5

如果您使用的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}}

If you are using jsonpickle the easiest way should look as below.

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)

After Json serialization you will have as expected {"status": 0} instead of

{"status": {"__objclass__": {"py/type": "__main__.Status"}, "_name_": "success", "_value_": 0}}

回答 6

这为我工作:

class Status(Enum):
    success = 0

    def __json__(self):
        return self.value

不必更改其他任何内容。显然,您只会从中获得该值,并且如果您想稍后将序列化的值转换回枚举,则需要做一些其他工作。

This worked for me:

class Status(Enum):
    success = 0

    def __json__(self):
        return self.value

Didn’t have to change anything else. Obviously, you’ll only get the value out of this and will need to do some other work if you want to convert the serialized value back into the enum later.