将 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

我怎么才能避免呢?

86420 次浏览

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

如果要反序列化回 Python,请参见 零的回答

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

from enum import IntEnum
import json


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


json.dumps(Status.success)

结果是:

'0'

如果您想将任意 enum.Enum成员编码为 JSON,然后解码 它作为相同的 enum 成员(而不仅仅是 enum 成员的 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函数依赖于使用 EnumEncoder编码的 JSON,或者与其行为相同的东西。

PUBLIC_ENUMS成员的限制是必要的,以避免恶意编写的文本被用于,例如,欺骗调用代码保存私有信息(例如,应用程序使用的秘密密钥)到一个不相关的数据库字段,然后从那里可以公开(见 https://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'}

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

我希望这能帮到别人。

我知道这很古老,但我觉得这能帮助人们。我刚刚讨论了这个问题,发现如果使用字符串枚举,那么将枚举声明为 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,那么这是一个很好的选择。

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

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

这对我很有效:

class Status(Enum):
success = 0


def __json__(self):
return self.value

不需要改变什么。显然,您只能从中获得值,如果您想稍后将序列化的值转换回枚举,则需要做一些其他工作。

你只需要继承 strint类:

from enum import Enum, unique


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

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

您甚至可以将上面提到的解决方案与 Enums 的自动值创建相结合。我将它与 Pydantic 和 FastAPI 结合使用,为 REST API 提供小写名称:

from enum import Enum, auto
import json


class StrEnum(str, Enum):
pass


# this creates nice lowercase and JSON serializable names
# https://docs.python.org/3/library/enum.html#using-automatic-values
class AutoNameLower(StrEnum):
def _generate_next_value_(name, start, count, last_values):
return name.lower()


class AutoNameLowerStrEnum(AutoNameLower):
pass


class MyActualEnum(AutoNameLowerStrEnum):
THIS = auto()
THAT = auto()
FOO = auto()
BAR = auto()


print(MyActualEnum.THIS)
print(json.dumps(MyActualEnum.THIS))
print(list(MyActualEnum))

控制台:

>>> MyActualEnum.THIS
>>> "this"
>>> [<MyActualEnum.THIS: 'this'>, <MyActualEnum.THAT: 'that'>, <MyActualEnum.FOO: 'foo'>, <MyActualEnum.BAR: 'bar'>]