目錄
一、為什么要談 JSON
二、最快速上手:兩把鑰匙 dumps 與 loads
三、深入 dumps:參數是魔法棒
四、深入 loads:把風險擋在門外
五、文件級序列化:dump 與 load
六、處理中文與編碼陷阱
七、異常場景與調試技巧
八、實戰案例:增量保存爬蟲結果
九、對比其他序列化方案
十、總結
一、為什么要談 JSON
如果把程序運行時的內存比喻成熱鬧的城市,那么 JSON 就是連接城市與鄉村的高速公路。城市需要把新鮮果蔬送往鄉村冷庫(持久化),鄉村也要把冷藏好的貨物送回城市(加載)。JSON 憑借“純文本、語言無關、可讀性高”三大優勢,成為公路網中最繁忙的一條。Python 標準庫自帶的高速收費站 json 模塊,讓我們在不安裝任何第三方包的情況下,即可順暢通行。
二、最快速上手:兩把鑰匙 dumps 與 loads
json 模塊最常用的兩把鑰匙只有四個字母:
dumps 把 Python 對象變成 JSON 字符串
loads 把 JSON 字符串還原成 Python 對象
下面給出一段“零門檻”代碼,讓你瞬間體會二者的默契配合。
import jsonperson = {"name": "Alice", "age": 28, "skills": ["Python", "Go"]}# 序列化:Python dict -> JSON str
json_str = json.dumps(person, ensure_ascii=False, indent=2)
print("序列化結果:")
print(json_str)# 反序列化:JSON str -> Python dict
new_person = json.loads(json_str)
print("\n反序列化結果:")
print(new_person, type(new_person))
控制臺會打印出整齊縮進的 JSON,再原封不動地回到字典。ensure_ascii=False 讓中文不再被轉義成 \uXXXX,indent=2 則滿足了人類閱讀的需求。短短七行,就完成了內存與文本之間的第一次握手。
三、深入 dumps:參數是魔法棒
很多人止步于 dumps(obj),卻不知道它還有五個常用參數,可以讓輸出更優雅、體積更小。
-
separators
默認情況下 dumps 使用 (', ', ': ') 作為分隔符,空格讓 JSON 更美觀,卻讓字節數膨脹。若要把數據塞進網絡包,可以去掉空格。
compact = json.dumps(person, separators=(',', ':'))
print(compact) # {"name":"Alice","age":28,"skills":["Python","Go"]}
-
sort_keys
當接口文檔要求字段順序固定時,sort_keys=True 一鍵升序排序,避免肉眼比對差異時抓狂。
-
default
Python 世界里不只有 dict、list、str、int、float、bool、None,還有 datetime、Decimal、自定義類。JSON 標準并不認識它們,于是 default 充當翻譯官。
from datetime import datetimedef time_converter(o):if isinstance(o, datetime):return o.isoformat()raise TypeError(f"Object of type {type(o)} is not JSON serializable")event = {"created": datetime.utcnow()}
print(json.dumps(event, default=time_converter))
-
skipkeys
當字典的 key 不是 str、int、float、bool、None 時,默認會拋出 TypeError。設置 skipkeys=True 則靜默跳過這些 key,繼續序列化其余內容,這在調試日志中尤為實用。
四、深入 loads:把風險擋在門外
JSON 字符串可能來自不可信來源,因此 json.loads 提供了嚴格的語法校驗,拒絕一切非法值。除此之外,還有兩個冷門但實用的參數。
-
parse_float
默認會把帶小數點的數字解析成 float,若你使用 Decimal 避免精度誤差,可以指定解析器。
from decimal import Decimal
data = '{"price": 19.99}'
obj = json.loads(data, parse_float=Decimal)
print(obj["price"], type(obj["price"])) # 19.99 <class 'decimal.Decimal'>
-
object_hook
JSON 中的對象都是字典,若你希望直接映射成自定義類,可在反序列化階段動手腳。
class User:def __init__(self, name, age):self.name, self.age = name, agedef __repr__(self):return f"User(name={self.name}, age={self.age})"def dict_to_user(d):if {"name", "age"} <= d.keys():return User(d["name"], d["age"])return djson_user = '{"name": "Bob", "age": 30}'
user_obj = json.loads(json_user, object_hook=dict_to_user)
print(user_obj) # User(name=Bob, age=30)
五、文件級序列化:dump 與 load
當數據量較大或需要持久化到硬盤時,推薦用 dump 與 load 直接操作文件句柄,避免先把整個 JSON 字符串讀入內存。
data = {"users": [{"id": i, "name": f"user{i}"} for i in range(3)]}# 寫文件
with open("users.json", "w", encoding="utf-8") as f:json.dump(data, f, indent=2, ensure_ascii=False)# 讀文件
with open("users.json", encoding="utf-8") as f:data_back = json.load(f)
print(data_back["users"][0]["name"]) # user0
注意文件必須以文本模式打開,Windows 下若出現換行符困惑,可添加 newline='' 讓 Python 自動處理。
六、處理中文與編碼陷阱
瀏覽器或 Node.js 服務常把中文轉成 \u4e2d\u6587,導致日志不可讀。此時只需在 dumps 中設置 ensure_ascii=False,即可保留原始 UTF-8 字符。此外,文件讀寫顯式指定 encoding='utf-8' 可防止跨平臺亂碼。
七、異常場景與調試技巧
TypeError: Object of type set is not JSON serializable
集合 set 不是 JSON 基本類型,可先用 list() 轉換。JSONDecodeError: Expecting property name
字符串末尾多余逗號、單引號未轉義都會導致解析失敗,可使用 json.tool 命令行工具格式化校驗。性能瓶頸
大量數據循環調用 dumps 會觸發頻繁內存分配,可一次性構建大字典再序列化,或改用第三方庫 orjson、ujson。
八、實戰案例:增量保存爬蟲結果
假設我們在爬取 GitHub API,每 100 條記錄保存一次中間結果,防止程序崩潰丟失進度。
import requests, json, osdef crawl_and_save(keyword, pages=5):all_items = []for page in range(1, pages + 1):url = f"https://api.github.com/search/repositories?q={keyword}&page={page}"resp = requests.get(url, timeout=10)resp.raise_for_status()items = resp.json()["items"]all_items.extend(items)# 增量保存with open(f"github_{keyword}_page{page}.json", "w", encoding="utf-8") as f:json.dump(items, f, ensure_ascii=False, indent=2)# 合并結果with open(f"github_{keyword}_all.json", "w", encoding="utf-8") as f:json.dump(all_items, f, ensure_ascii=False, indent=2)if __name__ == "__main__":crawl_and_save("python", 3)
九、對比其他序列化方案
JSON 的優勢是“人讀、跨語言”,劣勢是“不支持復雜類型、體積較大”。如果需要更高性能或支持 Python 特有對象,可考慮:
pickle:速度快、支持任意對象,但僅限 Python 且存在安全風險。
MessagePack:二進制 JSON,體積更小,跨語言。
Protobuf:IDL 驅動,適合大型分布式系統。
選擇方案時,不要只追求技術酷炫,而應結合可讀性、兼容性、性能三角平衡。
十、總結
序列化是把“活的” Python 對象變成“可運輸”文本的過程,反序列化則是逆向復活。json 模塊提供了簡潔而強大的 API,讓我們以極低的成本完成數據持久化、網絡傳輸、配置管理。掌握 dumps、loads、dump、load 的細粒度參數,就能在優雅、安全、高效之間游刃有余。下次當你寫接口、寫爬蟲、寫工具腳本時,別忘了這條最熟悉也最容易被忽視的 JSON 高速通道。