2024年4月19日 星期五

Python 學習筆記 : 用 json 模組讀寫 .json 檔

最近在整理 Python 網頁爬蟲筆記, 覺得有兩個內建模組很常用, 第一個是 csv 模組, csv 檔是 Excel 試算表的純文字版, csv 模組提供讀寫 csv 檔的函式可快速地將檔案內容轉成 Python 字典或串列, 用法參考 : 


第二個是 json 模組, 這是 Python 用來處理 JSON 格式資料的內建模組, 與 csv 模組一樣, 直接匯入即可使用 :

import json  

JSON  (JavaScript Object Notation) 是一種結構化資料表示法, 源自 Javascript 的物件定義語法, 因為具有輕量與可讀性高的特性, 逐漸取代較複雜的 XML 成為最普及的 Web 資料交換格式, 也被許多程式語言支援, 很多 Web API 採用 JSON 格式來回傳, 很多 NoSQL 資料庫也以 JSON 來儲存資料, 參考 : 


JSON 資料格式有兩種語法, 第一種是與 Python 字典類似, 由鍵值對組成的 Javascript 物件, 語法如下 :

{"key1": "value1", "key2": "value2", ........}   
 
規則 : 
  • 鍵值對必須用大括號括起來, 每組鍵值對以逗號隔開
  • 鍵 (key) 必須是字串, 且須使用雙引號括起來, 不可用單引號
  • 值 (value) 可以是所有 Javascript 資料型態 : 數值, 字串, true/false, 陣列, null
  • 物件內不可使用任何註解
  • 交換資料時存成副檔名為 .json 檔案, 每一個 .json 檔只能含有一個物件 
例如 :

{"name": "Tony", "gender": "male", "age": 26, "height": 172.5, "married": true, "religion": null}

第二種 JSON 資料表示法為 Javascript 陣列, 語法與 Python 串列一樣, 元素放在中括號裡面以逗號隔開, 可以是任何 Javascript 資料型態 :

[e1, e2, e3, ...]

例如 : 

["Tony", "male", 26, 172.5, true, null]

不論是陣列或物件, 在一個 JSON 檔裡面只能有一個陣列或一個物件, 如果有多個物件, 可以將其放入一個陣列裡成為物件陣列, 例如 : 

[{"name": "Tony", "gender": "male", "age": 26, "height": 172.5, "married": true, "religion": null},
 {"name": "Jane", "gender": "female", "age": 22, "height": 167.2, "married": false, "religion": null}]

如果有多個陣列, 可以將其放入另一個陣列裡成為多維陣列, 例如 : 

[["Tony", "male", 26, 172.5, true, null], ["Jane", "female", 22, 167.2, false, null]]

注意, 這些 JSON 資料都是以字串形式做交換, json 模組提供了 loads() 與 dumps() 函式可用來在 JSON 字串與 Python 字典之間做轉換. 


1. 檢視 json 模組內容 :   

匯入 json 模組後可以用 dir() 函式檢視其內容 : 

>>> import json      
>>> dir(json)   
['JSONDecodeError', 'JSONDecoder', 'JSONEncoder', '__all__', '__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '_default_decoder', '_default_encoder', 'codecs', 'decoder', 'detect_encoding', 'dump', 'dumps', 'encoder', 'load', 'loads', 'scanner']     

也可以用下列自訂模組 members 之 list_members() 函式來進一步了解那些是類別與函式 :

# members.py
import inspect 
def varname(x): 
    return [k for k,v in inspect.currentframe().f_back.f_locals.items() if v is x][0]
def list_members(parent_obj):
    members=dir(parent_obj)
    parent_obj_name=varname(parent_obj)       
    for mbr in members:
        child_obj=eval(parent_obj_name + '.' + mbr) 
        if not mbr.startswith('_'):
            print(mbr, type(child_obj))  

將此函式存成 members.py 模組, 放在目前供作目錄下, 然後匯入其 list_members() 函式來檢視 json 模組 : 

>>> from members import list_members    
>>> list_members(json)   
JSONDecodeError <class 'type'>
JSONDecoder <class 'type'>
JSONEncoder <class 'type'>
codecs <class 'module'>
decoder <class 'module'>
detect_encoding <class 'function'>
dump <class 'function'>
dumps <class 'function'>
encoder <class 'module'>
load <class 'function'>
loads <class 'function'>
scanner <class 'module'>

常用的函式如下表 :


 json 常用函式 說明
 load(file) 從檔案參考 file 讀取 JSON 資料為 Python 字典後傳回
 loads(str) 從 JSON 字串 str 讀取 JSON 資料為 Python 字典後傳回
 dump(data, file) 將 Python 字典寫入 .json 檔案參考 file
 dumps(data [, sort_keys, indent]) 將 Python 字典轉成 JSON 字串後傳回


這四個函式會在 JSON 字串與 Python 字典之間進行轉換, 注意有無 s 的差別在於, 有 s 的處理對象是 JSON 字串; 而無 s 的則是 ,json 檔案. 

Javascript 與 Python 資料類型之對照如下表 :


 JSON 資料類型 Python 資料類型
 物件 object 字典 dict
 陣列 array 串列 list
 字串 string 字串 str
 整數 int 整數 int
 浮點數 Number 浮點數 float
 布林值 true 布林值 True
 布林值 false 布林值 False
 空值 null 空值 None


注意, Javascript 的 true/false 對應於 Python 的 True/False, 而 Javascript 的 null 則對應於 Python 的 None. 


2. 呼叫 json.loads() 將 JSON 字串轉成 Python 字典/串列 :   

json.loads(str) 用來將 JSON 字串轉成 Python 字典, 傳入值為一個 JSON 字串, 傳回值為一個 Python 字典或串列, 例如 :   

>>> json_str='{"name": "Tony", "gender": "male", "age": 26, "height": 172.5, "married": true, "religion": null}'    
>>> obj=json.loads(json_str) 
>>> type(obj)   
<class 'dict'>
>>> obj    
{'name': 'Tony', 'gender': 'male', 'age': 26, 'height': 172.5, 'married': True, 'religion': None}

可見 json.loads() 將 JSON 字串載入後轉成 Python 字典, 其值也轉成對應之 Python 資料型態. 

其次來看多個物件放在陣列的情況, 如上所述, JSON 資料中只能有一個 Javascript 物件, 若有多個物件要放在陣列中成為物件陣列 :

>>> json_str='[{"name": "Tony", "gender": "male", "age": 26, "height": 172.5, "married": true, "religion": null}, {"name": "Jane", "gender": "female", "age": 22, "height": 167.2, "married": false, "religion": null}]'     # JSON 資料為兩個物件組成之陣列
>>> obj=json.loads(json_str)     
>>> type(obj)   
<class 'list'>
>>> obj   
[{'name': 'Tony', 'gender': 'male', 'age': 26, 'height': 172.5, 'married': True, 'religion': None}, {'name': 'Jane', 'gender': 'female', 'age': 22, 'height': 167.2, 'married': False, 'religion': None}]

可見 loads() 會將 JSON 字串轉成字典串列. 


3. 呼叫 json.dumps() 將 Python 字典/串列轉成 JSON 字串 :    

dumps() 是 loads() 的反函式, 它可將 Python 字典/串列轉成 JSON 字串, 例如 : 

>>> data={'name': 'Tony', 'gender': 'male', 'age': 26, 'height': 172.5, 'married': True, 'religion': None}   
>>> json_str=json.dumps(data)   
>>> type(json_str)   
<class 'str'>   
>>> json_str      
'{"name": "Tony", "gender": "male", "age": 26, "height": 172.5, "married": true, "religion": null}'

下面是將字典串列轉成 JSON 字串的測試 :

>>> data=[{'name': 'Tony', 'gender': 'male', 'age': 26, 'height': 172.5, 'married': True, 'religion': None}, {'name': 'Jane', 'gender': 'female', 'age': 22, 'height': 167.2, 'married': False, 'religion': None}]   
>>> json_str=json.dumps(data)    
>>> json_str    
'[{"name": "Tony", "gender": "male", "age": 26, "height": 172.5, "married": true, "religion": null}, {"name": "Jane", "gender": "female", "age": 22, "height": 167.2, "married": false, "religion": null}]'

當物件多的時候會很難閱讀, 可用 indent 參數來調整縮排, 例如 : 

>>> json_str=json.dumps(data, indent=4)     
>>> json_str   
'[\n    {\n        "name": "Tony",\n        "gender": "male",\n        "age": 26,\n        "height": 172.5,\n        "married": true,\n        "religion": null\n    },\n    {\n        "name": "Jane",\n        "gender": "female",\n        "age": 22,\n        "height": 167.2,\n        "married": false,\n        "religion": null\n    }\n]'

要用 print() 輸出才看得到效果 :

>>> print(json_str)    
[
    {
        "name": "Tony",
        "gender": "male",
        "age": 26,
        "height": 172.5,
        "married": true,
        "religion": null
    },
    {
        "name": "Jane",
        "gender": "female",
        "age": 22,
        "height": 167.2,
        "married": false,
        "religion": null
    }
]


4. 呼叫 json.dump() 將 Python 字典/串列寫入 .json 檔案 :    

呼叫 json.dump() 並傳入 Python 字典/串列與檔案參考可將其寫入 json 檔案 (ANSI 編碼) : 

>>> data={'name': 'Tony', 'gender': 'male', 'age': 26, 'height': 172.5, 'married': True, 'religion': None}  
>>> with open('test.json', 'w') as f:  
    json.dump(data, f)   

開啟目前工作目錄下的 test.json 內容如下, 編碼格式為 ANSI :

{"name": "Tony", "gender": "male", "age": 26, "height": 172.5, "married": true, "religion": null}

可見 dump() 已經將 true/false 轉成 True/False, None 轉成 null 了, 同時也把原先字典使用的單引號全部轉成雙引號, 因為 JSON 格式中字串必須使用雙引號. 

如果字典中所有非英文字母會轉成 unicode, 例如將 name 欄位值改成 "金秀賢" :  

{"name": "\u91d1\u79c0\u8ce2", "gender": "male", "age": 26, "height": 172.5, "married": true, "religion": null}


5. 呼叫 json.load() 載入 .json 檔案轉成 Python 字典/串列 :   

呼叫 json.load(file) 可以從檔案 file 讀取 JSON 資料, 它會傳回一個字/串列, 例如上面範例的檔案 test.json : 

{"name": "\u91d1\u79c0\u8ce2", "gender": "male", "age": 26, "height": 172.5, "married": true, "religion": null}

>>> with open('test.json', 'r') as f:   
    data=json.load(f)   
                    
>>> data  
{'name': '金秀賢', 'gender': 'male', 'age': 26, 'height': 172.5, 'married': True, 'religion': None}

沒有留言 :