深入淺出 Python JSON 處理:從基礎概念到進階應用

深入淺出 Python JSON 處理:從基礎概念到進階應用

在現代網路應用與微服務架構中,資料交換成為各系統溝通的橋樑,而 JSON(JavaScript Object Notation)作為一種輕量且具高度可讀性的資料格式,正逐步取代 XML 成為主流。無論是在前端傳輸資料,還是後端 API 的回應,都離不開 JSON 的身影。對於 Python 開發者而言,掌握如何讀取、解析、編碼與儲存 JSON 資料,是提升專案效率與可靠性的必要技能。

本文章將從 JSON 的基本定義、結構與語法規則開始,接著詳細介紹在 Python 中 json 模組、套件,並搭配豐富範例展示如何處理 JSON 資料物件。除此之外,我們還會探討進階應用(例如自定義編碼與解碼)、命令列工具的使用,以及在實際專案中的注意事項與常見問題解析。希望通過本篇文章,你能夠深入了解 JSON 的工作原理,並靈活運用在各種實際情境中。

一、什麼是 JSON?

JSON 的定義與歷史

JSON 檔的全名為 JavaScript Object Notation,是由 Douglas Crockford 於 2001 年最先提出。儘管其語法靈感來自 JavaScript 的物件字面量,但 JSON 格式已經脫離了語言的限制,成為一種獨立的資料交換格式。它具有以下特點:

  • 輕量型(Light-Weight): 所生成的文件體積小,適合網路傳輸,減少頻寬佔用。
  • 基於文本(Text-Based): 以純文字呈現,無需特殊工具即可編輯和檢視字符串。
  • 可讀性高(Human-Readable): 結構清晰、語法簡單,方便人員閱讀與理解的字符串。
  • 穩定性(Stability): JSON 格式自誕生以來就非常穩定,即使多年後,舊有的 JSON 檔案仍能被現行系統正確解析。

正因為這些優點,JSON 成為 REST API 與許多網路應用的首選資料格式。

JSON 的基本語法與結構

JSON 主要由兩種結構組成:物件(Object)陣列(Array)

物件(Object)

物件由一對大括號 {} 包圍,其中包含以逗號分隔的多組「鍵(Key)」與「值(Value)」對。每個鍵必須是字串(必須用雙引號 ” 包圍),鍵與值之間用冒號 : 分隔。

範例:

{
  "name": "John",
  "age": 30,
  "city": "New York"
}

陣列(Array)

陣列由一對中括號 [] 包圍,其中的每個元素可以是任何合法的 JSON 資料型態(物件、陣列、字串、數字、布林值或 null),元素間用逗號 , 分隔。

範例:

[
  "apple",
  "banana",
  "orange"
]

綜合範例

以下是一個包含物件與陣列的綜合範例:

{
  "name": "John",
  "age": 30,
  "city": "New York",
  "children": [
    {
      "name": "Alice",
      "age": 5
    },
    {
      "name": "Bob",
      "age": 8
    }
  ]
}

1.3 JSON 與其他資料格式比較

與 XML 相比,JSON 的結構更簡潔且佔用空間更小,因而更適合現代網路傳輸需求。此外,JSON 資料的解析速度通常比 XML 快,因此在大規模資料交換場景中具有顯著優勢。儘管 YAML 也具備可讀性,但 JSON 的普及程度更高,在 API 與 Web 開發中擁有更廣泛的應用場景。

二、Python 中的 JSON 模組

Python 提供了一個強大的內建模組——json 模組,用於處理 JSON 資料的編碼(序列化)與解碼(反序列化)。通過 json 模組,我們可以輕鬆地將 Python 資料結構(例如 dict、list 等)轉換為 JSON 格式,也可以將 JSON 字串轉換回 Python 資料結構、資料類型。

2.1 Python 與 JSON 資料型態對照表

下表總結了 Python 與 JSON 之間的資料型態轉換關係:

Python 資料型態 JSON 表示
dict object
list、tuple array
str string
int、float number
True / False true / false
None null

這個對照表顯示了在轉換過程中,Python 的資料型態會如何對應到 JSON 中,反之亦然。這使得在跨語言資料交換時,可以確保資料格式的一致性。

2.2 基本函數介紹

Python json 模組主要提供以下函數:

  • json.dumps():將 Python 物件轉換為 JSON 字串(序列化)。
  • json.dump():將 Python 物件轉換為 JSON 格式,並寫入到檔案中。
  • json.loads():將 JSON 字串轉換為 Python 物件(反序列化)。
  • json.load():從檔案中讀取 JSON 文件資料,並轉換為 Python 物件。

您可以使用 json.dumps() 函數的多個參數來調整輸出的 JSON 字符串格式,包括:

  • indent=None:​當未設置縮進時,輸出的 JSON 字符串將緊湊地排列,沒有額外的縮進或換行。
  • default=None:​如果未提供自定義的序列化函數,當遇到無法序列化的對象時,json.dumps() 會引發 TypeError。
  • separators=None:​在未指定分隔符時,預設使用 (‘, ‘, ‘: ‘),即項目之間用逗號加空格分隔,鍵和值之間用冒號加空格分隔。
  • parse_int=None:​此可選參數允許您指定一個函數,該函數將被用來解析 JSON 中的整數值。預設情況下,json.loads() 使用內建的 int() 函數來解析整數。如果您希望將 JSON 中的整數解析為其他類型(例如 float),可以提供自定義的函數。
  • parse_float=None:​此可選參數允許您指定一個函數,該函數將被用來解析 JSON 中的浮點數值。預設情況下,json.loads() 使用內建的 float() 函數來解析浮點數。如果您希望將 JSON 中的浮點數解析為其他類型(例如 decimal.Decimal),可以提供自定義的函數。
  • object_pairs_hook=None:​此可選參數允許您指定一個函數,該函數將被用來處理 JSON 對象中的鍵值對列表。當解析 JSON 對象時,object_pairs_hook 會接收一個有序的鍵值對列表,並返回一個自定義的對象。這在需要保持鍵的順序或實現自定義對象時特別有用。如果同時指定了 object_hook 和 object_pairs_hook,則 object_pairs_hook 具有優先權。
  • ensure_ascii:​控制非 ASCII 字符的處理。設置為 True 時,所有非 ASCII 字符會被轉義;設置為 False 時,則輸出原始字符。​

這些參數允許您根據需要自定義 JSON 的輸出格式。

接下來,我們將分別介紹這些函數的使用方法及實作範例。

三、JSON 與 Python 資料轉換實作

3.1 JSON 字串轉 Python 物件

方法一:使用 json.load() 讀取檔案

假設有一個名為 test.json 的 JSON 檔案,其內容如下:

{
  "Student 1": {
    "Student_id": 126,
    "Name": "Jack",
    "Score": 99,
    "Friends": ["Tim", "Jen", "Ken"]
  },
  "Student 2": {
    "Student_id": 128,
    "Name": "Ken",
    "Score": 99,
    "Friends": ["Jack", "Jessy", "Cathy"]
  }
}

使用 json.load() 直接讀取檔案內容並轉換為 Python 字典:

import json

# 讀取 JSON 檔案並轉換為 Python dict
with open('test.json', 'r') as f:
    python_dict = json.load(f)

print("Python Dict:", python_dict)
print("Type:", type(python_dict))

執行結果會顯示一個 Python 字典,並印出其型別為 <class ‘dict’>。

方法二:使用 json.loads() 處理 JSON 字串

如果先將 JSON 檔案內容讀取為字串,再使用 json.loads() 將其轉換為 Python 物件:

import json

# 先讀取整個 JSON 檔案為字串
with open('test.json', 'r') as f:
    json_text = f.read()

print("JSON Format:", json_text)
python_dict = json.loads(json_text)
print("Python Dict:", python_dict)
print("Type:", type(python_dict))

這兩種方法各有優點,json load 更適合直接從檔案讀取,而 json loads 則適用於需要先處理或修改字串的情境。

3.2 Python 物件轉 JSON 字串

方法一:使用 json dump 寫入檔案

假設我們有一個 Python 字典,需要將其儲存為 JSON 檔案:

import json

python_dict = {'Name': "Jack", 'Score': 99}

# 將 Python dict 寫入 JSON 檔案
with open("python2json.json", 'w') as f:
    json.dump(python_dict, f)

這段程式碼會將 Python 字典轉換為 JSON 格式,並儲存到 python2json.json 檔案中。

方法二:使用 json dumps 生成 JSON 字串

如果我們希望先生成 JSON 字串再進行其他處理,例如顯示在介面上或進行網路傳輸:

import json

python_dict = {'Name': "Jack", "Score": 99}
json_str = json.dumps(python_dict)
print("JSON Format:", json_str)
print("Type:", type(json_str))

# 將 JSON 字串寫入檔案
with open('python2json1.json', 'w') as f:
    f.write(json_str)

這樣可以靈活地將資料轉換為 JSON 字符串後進行儲存或傳輸。

3.3 格式化與美化 JSON 輸出

在實際專案中,儲存的 JSON 資料若排版混亂,對於後續的維護與除錯會非常不便。因此可以使用 indent 參數來進行美化格式化,使輸出結果更具可讀性。

範例一:使用 json.dumps() 的 indent 參數

import json

python_dict = {'Name': "Jack", 'Score': 99}
pretty_json = json.dumps(python_dict, indent=4)
print("Formatted JSON:", pretty_json)

# 儲存格式化後的 JSON 至檔案
with open('python2json_pretty.json', 'w') as f:
    f.write(pretty_json)

範例二:使用 json.dump() 直接寫入格式化檔案

import json

python_dict = {
    "Student A": {
        "Student_id": 126,
        "Name": "Jack",
        "Score": 99,
        "Friends": ["Tim", "Jen", "Cindy"]
    },
    "Student B": {
        "Student_id": 128,
        "Name": "Ken",
        "Score": 98,
        "Friends": ["Jack", "Jessy", "Cathy"]
    }
}

with open('python2json_pretty2.json', 'w') as f:
    json.dump(python_dict, f, indent=4)

設定 indent=4 表示每一層縮排 4 個空格,讓 JSON 結構清晰明瞭。

四、進階應用:自定義 JSON 編碼與解碼

在實際開發中,有時候會遇到 Python 內建資料型態無法直接序列化的情況(例如 complex 物件、日期時間物件或自定義類別)。這時可以透過自定義編碼器與解碼器來解決此問題。

4.1 自定義編碼器

可以藉由傳入 default 函數參數,或繼承 JSONEncoder 類別來擴充 JSON 的編碼功能。以下範例展示如何處理 Python 中的 complex 物件:

import json

def custom_json(obj):
    if isinstance(obj, complex):
        # 將 complex 物件轉換成可序列化的字典格式
        return {'__complex__': True, 'real': obj.real, 'imag': obj.imag}
    # 如果不是 complex,則拋出 TypeError
    raise TypeError(f'Object of type {type(obj)} is not JSON serializable')

# 序列化 complex 物件
complex_obj = 2 + 3j
json_str = json.dumps(complex_obj, default=custom_json)
print("Complex JSON:", json_str)

透過自定義函數,我們可以將無法直接序列化的物件轉換成字典,再由 json 模組處理。

4.2 自定義解碼器

若 JSON 中包含自定義標記(例如上述 complex 物件轉換後的字典格式),可以在解碼時使用 object_hook 將其轉換回原始物件:

import json

def as_complex(dct):
    if '__complex__' in dct:
        return complex(dct['real'], dct['imag'])
    return dct

# 假設從 JSON 取得的字串包含 complex 標記
json_complex = '{"__complex__": true, "real": 2, "imag": 3}'
restored_obj = json.loads(json_complex, object_hook=as_complex)
print("Restored Complex Object:", restored_obj)
print("Type:", type(restored_obj))

這樣做可以確保在反序列化時,特殊標記的物件能被正確還原。

五、使用 JSON 處理檔案 I/O

在實際應用中,經常需要將資料儲存至 JSON 檔案或從檔案中載入資料。以下分別展示如何進行這些操作。

5.1 儲存 JSON 檔案

假設我們有一筆資料需要儲存:

import json

data = {
    "name": "crab",
    "age": 18,
    "city": "New York",
    "skills": ["Python", "Machine Learning", "Data Analysis"]
}
json_path = 'data.json'

with open(json_path, "w") as json_file:
    json.dump(data, json_file, indent=4)
print("JSON 資料已成功寫入檔案")

這段程式碼將 Python 字典資料轉換成 JSON 格式並以美化格式寫入檔案,方便後續檢視與維護。

5.2 讀取 JSON 檔案

同理,從檔案中載入 JSON 資料:

import json

json_path = 'data.json'

with open(json_path, "r") as json_file:
    data = json.load(json_file)

print("讀取到的 JSON 資料:", data)

讀取過程中,json 模組會自動將 JSON 轉換為相應的 Python 資料型態,方便進一步處理。

六、進階主題與最佳實踐

6.1 JSON 處理中的性能考量

在大量資料處理時,JSON 的解析速度和內存消耗都是需要考慮的因素。以下是一些建議:

  • 檔案分割: 當 JSON 資料量巨大時,可以將資料分割成多個檔案,或使用 JSON Lines 格式(每行一個 JSON 物件),以減少單次載入的記憶體壓力。
  • 流式處理: 使用 json.load() 或 json.dump() 時,應盡量避免一次性載入超大檔案,可考慮分批讀取或使用流式處理技術。
  • 錯誤處理: 當處理不可信來源的 JSON 資料時,要格外注意資安問題,避免因解析錯誤導致資源耗盡或安全漏洞。

6.2 與其他序列化工具的比較

除了 JSON,Python 還提供了 pickle、marshal 等序列化工具。各工具優缺點比較如下:

序列化工具 優點 缺點
JSON 可讀性高、跨語言支援、輕量 僅支援基本資料型態,不支援自定義類別的原生序列化
pickle 能序列化大部分 Python 資料型態 不安全、不適用於跨語言資料交換,且版本間兼容性較差
marshal 運行速度快 格式不穩定、僅限 Python 內部使用

總結來看,若需進行跨平台、跨語言的資料交換,JSON 是最佳選擇;而在單一 Python 環境內,pickle 可能會提供更靈活的序列化支援,但要特別注意安全性問題。

6.3 常見錯誤與解決方案

在使用 json 模組時,常見的錯誤包括:

  • JSONDecodeError: 當 JSON 格式不正確時(例如缺少引號、逗號位置錯誤等),解析過程會引發該錯誤。解決方法:檢查 JSON 字串的格式與標點符號是否符合標準。
  • TypeError: 在序列化非基本型態資料(例如自定義類別、complex 物件)時,會引發此錯誤。解決方法:透過自定義 default 函數或繼承 JSONEncoder 類別來實現轉換。
  • UnicodeDecodeError: 當 JSON 檔案的編碼與預期不符時,可能會出現此錯誤。解決方法:確保檔案使用 UTF-8 或其他支援的編碼格式,或在讀取時指定正確的編碼參數。

七、命令列工具與輔助工具

7.1 使用 json.tool 命令列工具

Python 提供的 json.tool 命令列工具能夠輕鬆檢查 JSON 格式並進行美化排版。在命令列執行以下指令:

echo '{"json": "obj"}' | python -m json.tool

系統將回傳格式化後的 JSON,方便檢查格式正確性。若 JSON 資料錯誤,工具也會輸出詳細錯誤訊息,幫助使用者迅速定位問題。

7.2 整合至開發流程

在開發 API 與資料交換系統時,建議採用以下最佳實踐:

  • 自動化測試: 使用單元測試檢查 JSON 轉換過程,確保每次修改後資料格式仍符合預期。
  • 日誌記錄: 將 JSON 資料的讀取與寫入錯誤記錄下來,以便進行後續除錯與效能分析。
  • 安全性檢查: 對於外部來源的 JSON 資料,必須設定大小限制、格式校驗,並避免過度依賴自動解析,以防範惡意攻擊(例如拒絕服務攻擊)。

關於 Python JSON 的常見問題 (FAQ)

Q1:JSON 為何在現代網路應用中如此普及?

A1:JSON 格式輕量、可讀性高且基於文本,能夠在多種程式語言中通用。這使得 JSON 成為跨平台資料交換的理想選擇,特別適合 REST API 的設計與實現。

Q2:如何選擇使用 json.load() 與 json.loads()?

A2:若資料存放在檔案中,建議使用 json.load() 直接從檔案讀取並解析;若資料已經是字串格式或需要進行預處理,則使用 json.loads() 更為合適。

Q3:如何處理 Python 中無法直接序列化的物件?

A3:可透過定義 default 函數,或者繼承 JSONEncoder 類別來處理特殊物件,例如 complex 物件、日期時間物件或自定義類別。

Q4:如何確保 JSON 輸出結果的可讀性?

A4:可以在呼叫 json.dumps() 或 json.dump() 時傳入 indent 參數,例如 indent=4,這樣會自動對每層資料進行縮排,提升輸出的可讀性。

Q5:處理不可信來源的 JSON 資料時應注意哪些安全問題?

A5:對於外部來源的 JSON 資料,應設置大小限制、進行格式校驗,並使用 try/except 處理潛在的解析錯誤,以防止因惡意資料導致系統資源耗盡或安全漏洞。

總結

本文從 JSON 的定義與基本語法開始,深入講解了 Python 中 json 模組的使用方法,包括如何進行 JSON 與 Python 資料之間的轉換、格式化輸出、自定義編碼與解碼等進階應用。同時,我們也探討了 JSON 資料處理中的性能考量、與其他序列化工具的比較,以及常見錯誤的解決方案與命令列工具的輔助應用。

資料來源

返回頂端