1. 程式人生 > 其它 >JSON模組使用

JSON模組使用

技術標籤:pythonpython

json — JSON encoder and decoder

JSON(JavaScript Object Notation) 是一種輕量級的資料交換格式。易於人閱讀和編寫。同時也易於機器解析和生成。它基於JavaScript Programming Language, Standard ECMA-262 3rd Edition - December 1999的一個子集。JSON採用完全獨立於語言的文字格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使JSON成為理想的資料交換語言。

JSON建構於兩種結構:

  • “名稱/值”對的集合(A collection of name/value pairs)
    不同的語言中,它被理解為物件(object),紀錄(record),結構(struct),字典(dictionary),雜湊表(hash table),有鍵列表(keyed list),或者關聯陣列 (associative array)。
  • 值的有序列表(An ordered list of values)
    在大部分語言中,它被理解為陣列(array)

這些都是常見的資料結構。事實上大部分現代計算機語言都以某種形式支援它們。這使得一種資料格式在同樣基於這些結構的程式語言之間交換成為可能。

對簡單資料型別的encoding 和 decoding

import json
obj = [[1,2,3],123,123.123,'abc',{'key1':(1,2,3),'key2':(4,5,6)}] # 轉換列表為JSON字串
encodedjson = json.dumps(obj)
print(repr(obj))
print(encodedjson)
print(json.loads(encodedjson)) # loads方法返回了原始的物件,但是仍然發生了一些資料型別的轉化

output:

[[1, 2, 3], 123, 123.123, 'abc', {'key1': (1, 2, 3), 'key2': (4, 5, 6)}]
[[1, 2, 3], 123, 123.123, "abc", {"key1": [1, 2, 3], "key2": [4, 5, 6]}]
[[1, 2, 3], 123, 123.123, 'abc', {'key1': [1, 2, 3], 'key2': [4, 5, 6]}]

簡單型別通過encode之後跟其原始的repr()輸出結果非常相似,但是有些資料型別進行了改變,例如上例中的元組則轉換為了列表。在json的編碼過程中,會存在從python原始型別向json型別的轉化過程; 而解碼時仍然會發生一些資料型別變化
具體的轉化對照如下:

json.load()

原型:json.load(fp, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)

  • object_hook
    object_hook是可選函式,會被反序列後的dict呼叫(形式ocject_hook(dict)), 其它返回的結果會取代dict作為json.loads()的返回結果。
    指定object_hook後,該函式會取代JSONDecoder類或其子類的default()方法被呼叫

  • cls
    用於指定自定義的JSONDecoder子類,用於對JSON字串進行反序列化。沒有指定時,預設使用JSONDecoder類。

對應JSONDecoder子類來說,如果指定json.JSONDecoder的object_hook: json.JSONDecoder.init(self, object_hook=function)
則將使用每個解碼的JSON物件的結果來呼叫object_hook,其返回值取代dict作為返回結果

json.dumps的引數使用

原型:json.dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)

  • cls:自定義JSONEncoder子類(一般會重寫default()方法以序列化其他型別物件);沒有指定cls時,預設使用JSONEncoder類編碼

  • default: 針對無法序列化的物件的呼叫函式。

其指定的函式會被obj呼叫,形如:default(obj),通常應返回一個可序列化物件(如:dict);JSONEncoder.encode將可序列化物件解碼後作為json.dump()的返回值

指定default函式後,該函式會取代JSONencoder類或其子類的default()方法被呼叫,驗證程式碼如下:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __repr__(self):
        return f"Person('{self.name}',{self.age})"
person = Person('ccc', 25)
def obj_to_dict(obj):
    print(f'call obj_to_dict')
    dd ={}
    dd['__class__'] = obj.__class__.__name__
    dd['__module__'] = obj.__module__
    dd.update(obj.__dict__)
    return dd

class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        print(f"call MyEncoder's default")
        dd ={}
        dd['__class__'] = obj.__class__.__name__
        dd['__module__'] = obj.__module__
        dd.update(obj.__dict__)
        return dd

dump = json.dumps(person, cls=MyEncoder, default=obj_to_dict)
print(dump)
# output
'''
call obj_to_dict
{"__class__": "Person", "__module__": "__main__", "name": "ccc", "age": 25}
'''
  • sort_key - 為真時,字典的輸出按key排序
  • indent - 指定序列化時每個元素的縮排(分行縮排)
data = {'b':789,'c':456,'a':123}
print(json.dumps(data, sort_keys=True, indent=4))

# output
'''
{
    "a": 123,
    "b": 789,
    "c": 456
}
'''
  • seperators:指定序列化時使用的分隔符,預設(’,’, ': '), 是(item_separator, key_separator)元組
data = {'b':789,'c':456,'a':123}
json.dumps(data, separators=(',', ':'))

#output
'''
'{"b":789,"c":456,"a":123}'
'''
  • skipkeys為True時,key不是基礎型別(str, int, float, bool, None)就會跳過,而不會丟擲TypeError
data = {'b':789,'c':456,(1,2):123}
print(json.dumps(data,skipkeys=True))
'''
{"b": 789, "c": 456}
'''

處理自己的資料型別

JSON模組也可以將自定義的資料型別、 自定義物件轉換為JSON字串。
JSON.dumps並不能直接將自定義物件轉化為JSON字串,因為json不支援這樣的自動化.
由於JSON的Object類和dict類相關聯,所以應先將自定義物件轉化為dict,然後再進行轉化處理

如:將複數序列化為JSON格式字串, 然後再反序列化

def df(obj): 
    if isinstance(obj, complex): 
        return {'__complex__': True, 'real': obj.real, 'imag': obj.imag}
    return json.JSONEncoder.default(obj) 

data = json.dumps(1+2j, default=df)
print(data)

def as_complex(dct):
    try:
        if dct['__complex__']:
            return complex(dct['real'], dct['imag'])
    except KeyError:
        pass
    return dct

print(json.loads(data, object_hook=as_complex))

# output
'''
{"__complex__": true, "real": 1.0, "imag": 2.0}
(1+2j)
'''

一個自定義的Person物件,定義在person.py模組中,如下:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __repr__(self):
        return f"Person('{self.name}',{self.age})"

將自定義物件序(如Person)列化為JSON格式的字串,有兩種方法可以使用

自定義default轉化函式

  • default轉化函式
    如果指定了default,則其應該是針對無法序列化的物件呼叫的函式。default(object)應該返回一個JSON可編碼版本的python資料型別(見轉化表),或者丟擲TypeError。
import json
import person

def obj_to_dict(obj): # 將物件轉為dict
    dd ={}
    dd['__class__'] = obj.__class__.__name__
    dd['__module__'] = obj.__module__
    dd.update(obj.__dict__)
    return dd
    
person = Person('ccc', 25)
print(obj_to_dict(person))  

def dict_to_obj(dd): # 將dict轉為物件
    try:
        class_name = dd.pop('__class__')
        module_name = dd.pop('__module__')
        
        # 下兩句是動態匯入person.py模組並獲取Person物件,如果已經person模組已經匯入當然使用'cls = globals()[class_name]'更好了
        module = __import__(module_name, fromlist=[class_name]) 
        cls = getattr(module, class_name) # 獲取person.Person類物件
        
        # Person類的例項化
        instan = cls(**dd)
    except KeyError:
        instan = dd
    
    return instan
    
dict_to_obj(obj_to_dict(person))

dump = json.dumps(person, default=obj_to_dict) # 使用obj_to_dict轉化person物件為dict, 再將對應與person物件的dict序列化為JSON字串
print(dump)

load = json.loads(dump, object_hook=dict_to_obj) # 將JSON字串反序列為dict,呼叫dict_to_obj將dict轉化為person物件
print(load)

#output
'''
{'__class__': 'Person', '__module__': '__main__', 'name': 'ccc', 'age': 25}
{"__class__": "Person", "__module__": "__main__", "name": "ccc", "age": 25}
Person('ccc',25)
'''

自定義JSON編碼和解碼子類

自定義繼承JSONEncoder和JSONDecoder的子類, 用自定義的編碼器和解碼器進行編碼(將py物件轉為JSON序列),解碼(將JSON序列轉為python物件)

import json
import person

class MyEncoder(json.JSONEncoder):
    '''自定義編碼類,重寫JSONEncoder的default方法'''
    def default(self, obj):  
        dd ={}
        dd['__class__'] = obj.__class__.__name__
        dd['__module__'] = obj.__module__
        dd.update(obj.__dict__)
        return dd
    
class MyDecoder(json.JSONDecoder):
    '''自定義解碼類'''
    def __init__(self):
        super().__init__(object_hook=self.dict_to_obj) # 指定父類JSONDecoder的object_hook轉化函式:轉化dict為自定義的obj物件
    
    def dict_to_obj(self,dd):
        try:
            class_name = dd.pop('__class__')
            module_name = dd.pop('__module__')
            module = __import__(module_name, fromlist=[class_name])
            cls = getattr(module, class_name)
            instan = cls(**dd)
        except KeyError:
            instan = dd

        return instan

person = Person('ccc', 25)

dump = json.dumps(person, cls=MyEncoder)
print(dump)

# load = json.loads(dump, cls=MyDecoder, object_hook=dict_to_obj)  # 不能同時指定cls和object_hook,會出現衝突
load = json.loads(dump, cls=MyDecoder)
print(load)

參考

Introducing JSON