将Enum成员序列化为JSON


96

如何将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

我该如何避免呢?

Answers:


52

如果您想将任意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'}

1
谢谢,零!很好的例子。
伊桑·弗曼

如果您的代码位于模块中(例如enumencoder.py),则必须将您从JSON解析的类导入dict。例如,在这种情况下,必须在模块enumencoder.py中导入Status类。
弗朗西斯科·曼努埃尔·加尔卡·博泰拉

我关心的不是恶意调用代码,而是对Web服务器的恶意请求。如您所述,私有数据可以在响应中公开,也可以用于操纵代码流。感谢您更新答案。如果主代码示例是安全的,那就更好了。
贾里德·戴卡德

1
@JaredDeckard,我很抱歉,您是对的,我是错的。我已经相应地更新了答案。感谢您的输入!这一直是有教育意义的(而且令人费解)。
比雷埃夫斯零(Zero Piraeus),2017年

这个选项会更合适if isinstance(obj, Enum):吗?
user7440787

113

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


1
谢谢。即使我主要反对多重继承,这也很整洁,这就是我要遵循的方式。无需额外的编码器:)
Vinicius Dantas

@madjardi,能否详细说明您遇到的问题?我从未遇到过字符串值与枚举中的属性名称不同的问题。我是否误解了您的评论?
贾斯汀·卡特

1
class LogLevel(str, Enum): DEBUG = 'Дебаг' INFO = 'Инфо'在这种情况下,enum with str无法正常工作(
madjardi

1
您还可以使用其他基本类型来实现此技巧,例如(我不知道如何在注释中设置其格式,但是要旨很明确:“ Shapes(int,Enum)类:square = 1 circle = 2”有效不需要编码器,谢谢,这是个好方法!
NoCake

70

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

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

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

from enum import IntEnum
import json

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

json.dumps(Status.success)

这将返回:

'0'

5
@AShelly:该问题被标记为Python3.4,并且此答案是3.4+特定的。
伊桑·弗曼

2
完善。如果ENUM是一个字符串,你可以使用EnumMeta,而不是IntEnum
bholagabbar

5
@bholagabbar:不,你会用Enum,可能与str混入-class MyStrEnum(str, Enum): ...
伊桑·弗曼

3
@bholagabbar,有趣。您应该发布解决方案作为答案。
伊森·弗曼

1
我将避免直接从继承EnumMeta,后者仅用作元类。相反,注意实施IntEnum 是一个班轮,你可以达到同样的strclass StrEnum(str, Enum): ...
yungchin '19

15

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


看起来不错,但是它将nameenum写入json字符串。更好的方法是使用value枚举。
eNca

枚举值可用于json.dumps(enum_obj, default=lambda x: x.value)
eNca

10

我喜欢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)

我希望这可以帮助别人。


为什么需要添加ToJson到数据模型?
于晨

2

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

-1

这为我工作:

class Status(Enum):
    success = 0

    def __json__(self):
        return self.value

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


2
我在文档中看不到任何描述该魔术方法的内容。您是否正在使用其他JSON库,或者在JSONEncoder某个地方有自定义?
0x5453
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.