2017-05-08 145 views
3

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

我已经写一个简单的测试的情况下,如下所示,这是我在一个干净的virtualenv已经测试:

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类的解决方案,但我需要Enum的值为字符串。我也看到了this answer,它讨论了与从另一个类继承的Enum相关的问题。但是,我的枚举仅继承自enum.Enum类,并正确地响应isinstance调用?

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

$ 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语句中的一个被触发)所以永远不要调用自定义的默认方法,并且无法序列化Enum?

任何帮助将不胜感激!

+0

相关:http://stackoverflow.com/a/43730306/674039 – wim

+0

您不能使用字符串以外的任何其他键。编码器无法在此处进行调解,这是一条硬性规则。 –

回答

4

除了字符串以外,您不能使用任何内容作为要转换为JSON的字典中的键。编码器不会给你任何其他选项; default挂钩仅针对未知类型的值调用,从不为键。

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

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

这递归处理您的字典键。请注意,我包含一个钩子;那么你可以选择如何枚举值转换为字符串:

def enum_names(key): 
    if isinstance(key, TestEnum): 
     return key.name 
    return str(key) 

json.dumps(convert_keys(test, enum_names)) 

您可以使用相同的功能,以扭转从JSON加载时的过程:

def names_to_enum(key): 
    try: 
     return TestEnum[key] 
    except KeyError: 
     return key 

convert_keys(json.loads(json_data), names_to_enum) 

演示:

>>> 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!'} 
+0

那么出色的工作!谢谢。遗憾的是没有办法通过编码器来实现这一点,文档并不像他们可以这样清楚。 –