将Python Enum编码为JSON

Encoding Python Enum to JSON

我有一个字典,其中一些键是枚举实例(enum.enum的子类)。我正试图使用文档中的自定义JSON编码器类将字典编码为JSON字符串。我只想让输出的JSON中的键是枚举名称的字符串。例如,{ TestEnum.one : somevalue }将被编码为{"one" : somevalue }

我编写了一个简单的测试用例,如下所示,我在干净的virtualenv中进行了测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import json

from enum import Enum

class TestEnum(Enum):
    one ="first"
    two ="second"
    three ="third"

class TestEncoder(json.JSONEncoder):
   """ Custom encoder class"""

    def default(self, obj):

        print("Default method called!")

        if isinstance(obj, TestEnum):
            print("Seen TestEnum!")
            return obj.name

        return json.JSONEncoder.default(self, obj)

def encode_enum(obj):
   """ Custom encoder method"""

    if isinstance(obj, TestEnum):
        return obj.name
    else:
        raise TypeError("Don't know how to decode this")

if __name__ =="__main__":

    test = {TestEnum.one :"This",
            TestEnum.two :"should",
            TestEnum.three :"work!"}

    # Test dumps with Encoder method
    #print("Test with encoder method:")
    #result = json.dumps(test, default=encode_enum)
    #print(result)

    # Test dumps with Encoder Class
    print("Test with encoder class:")
    result = json.dumps(test, cls=TestEncoder)
    print(result)

我无法成功地对字典进行编码(使用python 3.6.1)。我不断地得到TypeError: keys must be a string错误和自定义编码器实例的默认方法(通过json.dumps方法的cls参数提供)似乎从未被调用?我还试图通过json.dumps方法的default参数提供一个自定义编码方法,但这仍然不会触发。

我见过涉及intenum类的解决方案,但我需要枚举的值是字符串。我还看到了这个答案,它讨论了一个与从另一个类继承的枚举相关的问题。但是,我的枚举只从基枚举继承。枚举类并正确响应isinstance调用?

当提供给json.dumps方法时,自定义类和方法都会生成TypeError。典型输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ python3 enum_test.py

Test with encoder class
Traceback (most recent call last):
  File"enum_test.py", line 59, in <module>
    result = json.dumps(test, cls=TestEncoder)
  File"/usr/lib64/python3.6/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File"/usr/lib64/python3.6/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File"/usr/lib64/python3.6/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
TypeError: keys must be a string

我假设问题是jsonEncoder类的encode方法假定它知道如何序列化枚举类(因为触发了iterencode方法中的一个if语句),因此从不调用自定义默认方法,并且最终未能序列化枚举?

任何帮助都将不胜感激!


在要转换为JSON的字典中,除了字符串之外,不能使用任何东西作为键。编码器没有提供任何其他选项;只对未知类型的值调用default钩子,而不对键调用。

将密钥转换为前面的字符串:

1
2
3
4
5
6
7
8
def convert_keys(obj, convert=str):
    if isinstance(obj, list):
        return [convert_keys(i, convert) for i in obj]
    if not isinstance(obj, dict):
        return obj
    return {convert(k): convert_keys(v, convert) for k, v in obj.items()}

json.dumps(convert_keys(test))

这将递归地处理字典键。注意,我包含了一个钩子;然后您可以选择如何将枚举值转换为字符串:

1
2
3
4
5
6
def enum_names(key):
    if isinstance(key, TestEnum):
        return key.name
    return str(key)

json.dumps(convert_keys(test, enum_names))

从JSON加载时,可以使用相同的函数来反转过程:

1
2
3
4
5
6
7
def names_to_enum(key):
    try:
        return TestEnum[key]
    except KeyError:
        return key

convert_keys(json.loads(json_data), names_to_enum)

演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> def enum_names(key):
...     if isinstance(key, TestEnum):
...         return key.name
...     return str(key)
...
>>> json_data = json.dumps(convert_keys(test, enum_names))
>>> json_data
'{"one":"This","two":"should","three":"work!"}'
>>> def names_to_enum(key):
...     try:
...         return TestEnum[key]
...     except KeyError:
...         return key
...
>>> convert_keys(json.loads(json_data), names_to_enum)
{<TestEnum.one: 'first'>: 'This', <TestEnum.two: 'second'>: 'should', <TestEnum.three: 'third'>: 'work!'}