Python:如何从 json 解码枚举类型

Python: How to decode enum type from json

class MSG_TYPE(IntEnum):
    REQUEST = 0
    GRANT = 1
    RELEASE = 2
    FAIL = 3
    INQUIRE = 4
    YIELD = 5

    def __json__(self):
        return str(self)

class MessageEncoder(JSONEncoder):
    def default(self, obj):
        return obj.__json__()

class Message(object):
    def __init__(self, msg_type, src, dest, data):
        self.msg_type = msg_type
        self.src = src
        self.dest = dest
        self.data = data

    def __json__(self):
        return dict (\
            msg_type=self.msg_type, \
            src=self.src, \
            dest=self.dest, \
            data=self.data,\
            )

    def ToJSON(self):
        return json.dumps(self, cls=MessageEncoder)

msg = Message(msg_type=MSG_TYPE.FAIL, src=0, dest=1, data="hello world")
encoded_msg = msg.ToJSON()
decoded_msg = yaml.load(encoded_msg)
print type(decoded_msg['msg_type'])

当调用 print type(decoded_msg['msg_type']) 时,我得到结果 <type 'str'> 而不是原来的 MSG_TYPTE 类型。我觉得我也应该写一个自定义 json 解码器,但有点困惑如何去做。有任何想法吗?谢谢。

When calling print type(decoded_msg['msg_type']), I get the result instead of the original MSG_TYPTE type.

嗯,是的,那是因为你告诉 MSG_TYPE 像这样编码自己:

def __json__(self):
    return str(self)

所以,这显然要解码回字符串。如果你不想要那样,想出一些独特的方法来编码值,而不是仅仅编码它们的字符串表示。

最常用的方法是使用 object 的某种特殊形式对所有自定义类型(包括枚举类型)进行编码——就像您为 Message 所做的一样。例如,您可以在 object 中放置一个 py-type 字段,它对对象的类型进行编码,然后其他字段的含义都取决于类型。理想情况下,您当然希望抽象出共性,而不是对同一事物进行 100 次硬编码。


I feel like I should also write a custom json decoder but kind of confused how to do that.

嗯,你读过the documentation了吗?你到底哪里糊涂了?您不会通过跟踪 Whosebug 问题来获得完整的教程……

假设您的所有类型都有一个特殊的 object 结构,您可以使用 object_hook 将值解码回原始值。例如,作为一个快速技巧:

class MessageEncoder(JSONEncoder):
    def default(self, obj):
        return {'py-type': type(obj).__name__, 'value': obj.__json__()}

class MessageDecoder(JSONDecoder):
    def __init__(self, hook=None, *args, **kwargs):
        if hook is None: hook = self.hook
        return super().__init__(hook, *args, **kwargs)
    def hook(self, obj):
        if isinstance(obj, dict):
            pytype = obj.get('py-type')
            if pytype:
                t = globals()[pytype]
                return t.__unjson__(**obj['value'])
        return obj

现在,在您的 Message class:

@classmethod
def __unjson__(cls, msg_type, src, dest, data):
    return cls(msg_type, src, dest, data)

你需要一个 MSG_TYPE.__json__ 那个 returns 一个字典,也许只是 {'name': str(self)},然后是一个 __unjson__ 做类似 getattr(cls, name).

现实生活中的解决方案可能应该让 classes 自己注册而不是按名称查找它们,或者应该处理按限定名称查找它们而不是直接去 globals() .你可能想让事物编码成 object 以外的东西——或者,如果不是,只是将 py-type 塞进对象而不是将它包装在另一个对象中。并且可能还有其他方法可以使 JSON 更紧凑 and/or 可读。一点点错误处理会很好。等等。


您可能想看看 jsonpickle 的实现 — 不是因为您想做与它完全相同的事情,而是想看看它是如何连接所有部分的。

在这种情况下覆盖编码器的默认方法无关紧要,因为您的对象永远不会传递给该方法。它被视为一个整数。

如果您 运行 编码器本身:

msg_type = MSG_TYPE.RELEASE
MessageEncoder().encode(msg_type)

您将获得:

'MSG_TYPE.RELEASE'

如果可以,请使用枚举,应该不会有任何问题。我也问过类似的问题: