Serialising an Enum member to JSON

后端 未结 7 1865
借酒劲吻你
借酒劲吻你 2020-12-13 11:52

How do I serialise a Python Enum member to JSON, so that I can deserialise the resulting JSON back into a Python object?

For example, this code:

相关标签:
7条回答
  • 2020-12-13 12:11

    I know this is old but I feel this will help people. I just went through this exact problem and discovered if you're using string enums, declaring your enums as a subclass of str works well for almost all situations:

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

    Will output:

    LogLevel.DEBUG
    "DEBUG"
    DEBUG
    LogLevel.DEBUG
    

    As you can see, loading the JSON outputs the string DEBUG but it is easily castable back into a LogLevel object. A good option if you don't want to create a custom JSONEncoder.

    0 讨论(0)
  • 2020-12-13 12:19

    I liked Zero Piraeus' answer, but modified it slightly for working with the API for Amazon Web Services (AWS) known as Boto.

    class EnumEncoder(json.JSONEncoder):
        def default(self, obj):
            if isinstance(obj, Enum):
                return obj.name
            return json.JSONEncoder.default(self, obj)
    

    I then added this method to my data model:

        def ToJson(self) -> str:
            return json.dumps(self.__dict__, cls=EnumEncoder, indent=1, sort_keys=True)
    

    I hope this helps someone.

    0 讨论(0)
  • 2020-12-13 12:25

    If you want to encode an arbitrary enum.Enum member to JSON and then decode it as the same enum member (rather than simply the enum member's value attribute), you can do so by writing a custom JSONEncoder class, and a decoding function to pass as the object_hook argument to json.load() or 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
    

    The as_enum function relies on the JSON having been encoded using EnumEncoder, or something which behaves identically to it.

    The restriction to members of PUBLIC_ENUMS is necessary to avoid a maliciously crafted text being used to, for example, trick calling code into saving private information (e.g. a secret key used by the application) to an unrelated database field, from where it could then be exposed (see http://chat.stackoverflow.com/transcript/message/35999686#35999686).

    Example usage:

    >>> 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'}
    
    0 讨论(0)
  • 2020-12-13 12:27

    If you are using jsonpickle the easiest way should look as below.

    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)
    
    

    After Json serialization you will have as expected {"status": 0} instead of

    {"status": {"__objclass__": {"py/type": "__main__.Status"}, "_name_": "success", "_value_": 0}}
    
    0 讨论(0)
  • 2020-12-13 12:28

    In Python 3.7, can just use json.dumps(enum_obj, default=str)

    0 讨论(0)
  • 2020-12-13 12:31

    The correct answer depends on what you intend to do with the serialized version.

    If you are going to unserialize back into Python, see Zero's answer.

    If your serialized version is going to another language then you probably want to use an IntEnum instead, which is automatically serialized as the corresponding integer:

    from enum import IntEnum
    import json
    
    class Status(IntEnum):
        success = 0
        failure = 1
    
    json.dumps(Status.success)
    

    and this returns:

    '0'
    
    0 讨论(0)
提交回复
热议问题