Python: How to decode enum type from json

对着背影说爱祢 提交于 2021-02-11 06:48:44

问题


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'])

When calling print type(decoded_msg['msg_type']), I get the result <type 'str'> instead of the original MSG_TYPTE type. I feel like I should also write a custom json decoder but kind of confused how to do that. Any ideas? Thanks.


回答1:


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

Well, yeah, that's because you told MSG_TYPE to encode itself like this:

def __json__(self):
    return str(self)

So, that's obviously going to decode back to a string. If you don't want that, come up with some unique way to encode the values, instead of just encoding their string representations.

The most common way to do this is to encode all of your custom types (including your enum types) using some specialized form of object—just like you've done for Message. For example, you might put a py-type field in the object which encodes the type of your object, and then the meanings of the other fields all depend on the type. Ideally you'll want to abstract out the commonalities instead of hardcoding the same thing 100 times, of course.


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

Well, have you read the documentation? Where exactly are you confused? You're not going to get a complete tutorial by tacking on a followup to a StackOverflow question…

Assuming you've got a special object structure for all your types, you can use an object_hook to decode the values back to the originals. For example, as a quick hack:

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

And now, in your Message class:

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

And you need a MSG_TYPE.__json__ that returns a dict, maybe just {'name': str(self)}, then an __unjson__ that does something like getattr(cls, name).

A real-life solution should probably either have the classes register themselves instead of looking them up by name, or should handle looking them up by qualified name instead of just going to globals(). And you may want to let things encode to something other than object—or, if not, to just cram py-type into the object instead of wrapping it in another one. And there may be other ways to make the JSON more compact and/or readable. And a little bit of error handling would be nice. And so on.


You may want to look at the implementation of jsonpickle—not because you want to do the exact same thing it does, but to see how it hooks up all the pieces.




回答2:


Overriding the default method of the encoder won't matter in this case because your object never gets passed to the method. It's treated as an int.

If you run the encoder on its own:

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

You'll get:

'MSG_TYPE.RELEASE'

If you can, use an Enum and you shouldn't have any issues. I also asked a similar question:

How do I serialize IntEnum from enum34 to json in python?



来源:https://stackoverflow.com/questions/29707219/python-how-to-decode-enum-type-from-json

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!