将枚举成员序列化为 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】:
你只需要从str
或int
类继承:
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的主要内容,如果未能解决你的问题,请参考以下文章
System.Text.Json:当json配置具有通用JsonStringEnumConverter时,如何将单个枚举序列化为数字[重复]