最近更新: 2010-04-09

Python json dumps custom type

使用 json.dumps() 將個體轉換成 JSON 文件時,Python json 模組可以自行處理絕大部份的 Python 內建型別資料,列表請見 Performs the following translations in decoding by default.。但是當轉換的個體為自定型別,或者是容器中包含自定型別資料時,你就必須指定 defaultcls 鍵值參數,指示 json.dumps() 如何轉換那些自定型別資料。

default keyword argument

default 鍵值參數指定自定型別資料的序列化函數或方法。該函數或方法必須接受一個參數。

當你指定 default 鍵值參數時,json.dumps() 仍將使用預設的 json.JSONEncoder 擷取與轉換資料內容。但是當資料內容的型別是 json.JSONEncoder 所不支援的自定型別時,它就會將這個資料丟給 default 指定的方法處理。

cls keyword argument

另一種處理自定型別轉換動作的方式是使用 cls 鍵值參數。cls 鍵值參數必須指定一個以 json.JSONEncoder 為基礎類別的子類別,而且至少應覆寫 default 方法。

當你指定 cls 鍵值參數,json.dumps() 將直接使用你指定的類別處理資料轉換動作。

大多數情形,我們只需要指定鍵值參數 default 為我們自定的序列化方法即可。當你想要改變 Python json 模組的預設序列化行為時,你才需要使用鍵值參數 cls 自定 JSONEncoder 子類別的方式。

來源個體的處理策略

容器

容器係指 Python 內建型別中的 list, tuple, dict 。json.dumps() 有能力一一取出容器中的元素,並進一步轉換元素的內容。但若容器中具有自定型別的元素,則你至少要以 default 指定自定的序列化方法,否則它將擲出 TypeError

# encoding: utf-8
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import json

d = {
    'a': 1,
    'b': 'b',
    'list': [1,2,3],
    'dict': {'x':1, 'y':2},
    'tuple': (3, 'a')
}

print json.dumps(d)
# 容器的元素皆為 Python 內建型別,json.dumps() 可以自行處理。

class T1(object):
    def __init__(self, value):
        self.value = value

d = {
    'a': T1(1),  # 容器內的元素,有一個是自定型別
    'b': 'b',
    'list': [1,2,3],
    'dict': {'x':1, 'y':2},
    'tuple': (3, 'a')
}

try:
    print json.dumps(d)
except TypeError, err:
    print str(err)
#TypeError: <__main__.T1 object at 0x7fd6ffa7ee50> is not JSON serializable

# 1. 使用自定的序列化函數
def my_json_encode(o):
    #print type(o)
    if isinstance(o, T1):
        return o.value
    return json.JSONEncoder.default(self, obj)

print json.dumps(d, default=my_json_encode)
# json 模組會走訪容器的每個元素,當該元素的資料型態是 json.JSONEncoder 不認得的
# 資料型態時,該元素才會被傳遞給 default 參數指示的序列化函數處理。
# 在此例中, d.a 欄位之型態為自定類別 T1 ,故 d.a 將會被傳給 my_json_encode()。

# 2. 使用自定的編碼類別
class my_json_encoder(json.JSONEncoder):
    def default(self, o):
        return my_json_encode(o)

print json.dumps(d, cls=my_json_encoder)

自定型別個體

當你直接將一個自定型別個體交給 json.dumps() 時,你必須指定 defaultcls 鍵值參數。因為 json.dumps() 預設使用的 json.JSONEncoder 類別不會處理自定型別,若你並未指定任何序列化方法或編碼類別時,它將直接擲出 TypeError

下列範例利用 Python 的名稱空間能力,將自定型別與搭配的 JSONEncoder 編碼類別放在一起(sample2.py)。而使用者放在另一個源碼文件(sample3.py)。

# encoding: utf-8
'''
sample2.py
'''
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import json

class C(object):
    def __init__(self):
        self.a = 1
        self.b = 'b'
        self.list = [1,2,3]
        self.dict = {'x': 1, 'y': 2}
        self.tuple = (3, 'a')
    
    def json_serialize(self):
        '''
        回傳一個 json.JSONEncoder 認得的內容。
        一般情形,會把 object 轉換成 dict 。
        '''
        result = {
            'a': self.a,
            'b': self.b,
            'list': self.list,
            'dict': self.dict,
            'tuple': self.tuple
        }
        return result        

class JSONEncoder(json.JSONEncoder):
    def default(self, o):
        '''
        回傳一個 json.JSONEncoder 認得的內容。
        一般情形,會把 object 轉換成 dict 。
        '''
        result = {
            'a': o.a,
            'b': o.b,
            'list': o.list,
            'dict': o.dict,
            'tuple': o.tuple
        }
        return result        

# encoding: utf-8
'''
sample3.py
'''

import sys
reload(sys)
sys.setdefaultencoding('utf-8')
import json

import sample2

if __name__ == '__main__':
    d = sample2.C()
    #print json.dumps(d)
    #TypeError: <__main__.C object at 0x7f5c1db0ed50> is not JSON serializable

    # 1. by default
    print json.dumps(d, default=sample2.C.json_serialize)
    
    # 2. by cls; 利用名稱空間,區分不同模組的 JSONEncoder。
    print json.dumps(d, cls=sample2.JSONEncoder)

由於 d 本身的資料型態就是 json.JSONEncoder 不認得的,所以它會直接把 d 傳給 C.json_serializer() 。附帶一提,在此你必須寫成 default=C.json_serializer,不能寫 default=d.json_serializer

樂多舊網址: http://blog.roodo.com/rocksaying/archives/12153263.html

樂多舊回應
hoamon@gmail.com(hoamon) (#comment-20621321)
Fri, 09 Apr 2010 08:51:55 +0800
太棒了,我從來沒想過,原來 json.py 有自定型別轉換的功能,每次遇到這種問題,我都是在 dumps 前,自己作轉換的。

有了這招,功夫省不少。