2017-05-08 36 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

那麼出色的工作!謝謝。遺憾的是沒有辦法通過編碼器來實現這一點,文檔並不像他們可以這樣清楚。 –