将枚举成员序列化为 JSON

Posted

技术标签:

【中文标题】将枚举成员序列化为 JSON【英文标题】:Serialising an Enum member to JSON 【发布时间】:2014-08-20 08:04:13 【问题描述】:

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

我知道这已经过时了,但我觉得这会对人们有所帮助。我刚刚解决了这个确切的问题,发现如果你使用字符串枚举,将你的枚举声明为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,这是一个不错的选择。

【讨论】:

谢谢。尽管我主要反对多重继承,但这很简洁,这就是我要采用的方式。不需要额外的编码器:) @madjardi,您能详细说明您遇到的问题吗?我从来没有遇到过字符串的值与枚举中的属性名称不同的问题。我误解了你的评论吗? class LogLevel(str, Enum): DEBUG = 'Дебаг' INFO = 'Инфо' 在这种情况下 enum with str 不能正常工作( 你也可以用其他基本类型来做这个技巧,例如(我不知道如何在 cmets 中格式化它,但要点很清楚:“class Shapes(int, Enum): square=1 circle=2" 不需要编码器就可以很好地工作。谢谢,这是一个很好的方法! 这个 str mixin 可能会产生意想不到的副作用:请参阅 this question。【参考方案2】:

正确答案取决于您打算如何处理序列化版本。

如果您要反序列化回 Python,请参阅 Zero's answer。

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

from enum import IntEnum
import json

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

json.dumps(Status.success)

这会返回:

'0'

【讨论】:

@AShelly:这个问题被标记为Python3.4,这个答案是3.4+特定的。 完美。如果您的 Enum 是一个字符串,您将使用 EnumMeta 而不是 IntEnum @bholagabbar: 不,你会使用Enum,可能还有str mixin -- class MyStrEnum(str, Enum): ... @bholagabbar,很有趣。您应该发布您的解决方案作为答案。 我会避免直接从EnumMeta 继承,这只是一个元类。相反,请注意 IntEnum is a one-liner 的实现,您可以使用 class StrEnum(str, Enum): ...str 实现相同的效果。【参考方案3】:

如果您想将任意 enum.Enum 成员编码为 JSON 然后解码 它作为同一个枚举成员(而不是简单地枚举成员的value 属性),您可以通过编写自定义JSONEncoder 类和一个解码函数来作为object_hook 参数传递给json.load() 或@ 987654323@:

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 成员的限制是必要的,以避免恶意制作的文本被用于,例如,欺骗调用代码将私人信息(例如应用程序使用的密钥)保存到不相关的数据库字段,从然后可以将其暴露在哪里(请参阅https://chat.***.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'

【讨论】:

谢谢,零!很好的例子。 如果您的代码在模块中(例如 enumencoder.py),则必须将您解析的类从 JSON 导入到 dict。例如,在这种情况下,您必须在模块 enumencoder.py 中导入类 Status 我关心的不是恶意调用代码,而是对 Web 服务器的恶意请求。正如您所提到的,私有数据可以在响应中公开,也可以用于操作代码流。感谢您更新您的答案。如果主代码示例是安全的,那就更好了。 @JaredDeckard 我很抱歉,你是对的,我错了。我已经相应地更新了答案。感谢您的输入!这具有教育意义(也具有教育意义)。 有没有办法用编码器类以某种方式“注释”枚举,以便默认使用编码器?【参考方案4】:

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

如果你想使用枚举值,你可以这样做

json.dumps(enum_obj, default=lambda x: x.value)

或者如果你想使用枚举名称,

json.dumps(enum_obj, default=lambda x: x.name)

【讨论】:

看起来不错,但它会将枚举的 name 写入 json 字符串。更好的方法是使用枚举的value 枚举值可以被json.dumps(enum_obj, default=lambda x: x.value)使用 但是,如果使用 Enum 作为键,这将不起作用。它仍然会抱怨“TypeError:键必须是 str、int、float、bool 或 None”——上面的大多数解决方案都没有考虑到键的用法。枚举应该是可散列的。【参考方案5】:

我喜欢 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【参考方案6】:

你只需要从strint类继承:

from enum import Enum, unique

@unique            
class StatusEnum(int, Enum):
    pending: int = 11                                      
    approved: int = 15                                       
    declined: int = 266

就是这样,它将使用任何 JSON 编码器进行序列化。

【讨论】:

请注意,这会以字符串形式序列化为枚举值,例如“11”或“15”。【参考方案7】:

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

【讨论】:

【参考方案8】:

这对我有用:

class Status(Enum):
    success = 0

    def __json__(self):
        return self.value

无需更改任何其他内容。显然,您只能从中获取值,如果您想稍后将序列化值转换回枚举,则需要做一些其他工作。

【讨论】:

我在docs 中没有看到任何描述这种神奇方法的内容。您是在使用其他 JSON 库,还是在某处有自定义 JSONEncoder 可能这个用户正在导入simplejson?

以上是关于将枚举成员序列化为 JSON的主要内容,如果未能解决你的问题,请参考以下文章

将json字符反序列化为枚举

System.Text.Json:当json配置具有通用JsonStringEnumConverter时,如何将单个枚举序列化为数字[重复]

将枚举序列化为字符串

C# - Web API - 将枚举序列化为带空格的字符串

枚举序列化 Json 与 XML

将 ES6 类对象序列化为 JSON