【问题标题】:Serialising an Enum member to JSON将枚举成员序列化为 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

我怎样才能避免这种情况?

【问题讨论】:

    标签: python json python-3.x serialization enums


    【解决方案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.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'}
    

    【讨论】:

    • 谢谢,零!很好的例子。
    • 如果您的代码在模块中(例如 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?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-01-24
      • 2013-09-04
      • 1970-01-01
      • 2018-10-11
      • 1970-01-01
      • 2015-11-11
      • 1970-01-01
      相关资源
      最近更新 更多