💡 开发中经常要存点临时配置、爬虫结果或者API响应缓存?用数据库太重,用普通文本手动解析又麻烦?今天的主角——Python标准库json,再加点第三方小工具,轻松搞定轻量级JSON文本存储与交互~
Python 中的 JSON 处理教程
JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,人好读机器也好解析,现在几乎是全场景通用的“通用语言”。本教程从基础到进阶,带你快速掌握Python中的JSON处理。
📌 JSON 基础
JSON 数据结构
JSON 只支持6种核心数据结构,非常简洁:
- 对象 🗂️:花括号
{} 包裹的键值对,键必须是双引号字符串
- 数组 📊:方括号
[] 包裹的有序值集合,可混合类型(但建议规范)
- 值:可以是字符串(双引号)、数字、布尔值(
true/false)、null、对象或数组
🔄 JSON 与 Python 类型对应关系
Python的json模块会自动做双向映射,记清楚这张表就行:
📖 读取 JSON 数据
从字符串加载(json.loads)
loads = load string,适合处理API返回的字符串、多行配置文本等。
import json
# 多行JSON字符串可以用三引号,合法JSON不强制缩进,但Python写好看点
json_str = '''
{
"name": "John",
"age": 30,
"city": "New York",
"hobbies": ["reading", "traveling"],
"married": false
}
'''
# 转换为Python对象
data = json.loads(json_str)
print(type(data)) # <class 'dict'>
print(data["name"]) # John
print(data["hobbies"][0]) # reading
从文件加载(json.load)
load = load file object,记得用with语句自动关闭文件。
import json
# 假设同目录下有data.json文件
try:
with open('data.json', 'r', encoding='utf-8') as f:
data = json.load(f) # 直接传文件对象
except FileNotFoundError:
print("文件不存在,创建默认数据...")
data = {"default": True}
print(data)
✍️ 写入 JSON 数据
转为JSON字符串(json.dumps)
dumps = dump string,三个必用的实用参数要记牢:
ensure_ascii=False:保留中文等非ASCII字符(不加会变成\uXXXX乱码)
indent=2或4:设置缩进层级,方便人阅读
sort_keys=True:按字典键排序,输出结果更稳定(便于比较)
import json
data = {
"name": "张三",
"age": 28,
"city": "北京",
"hobbies": ["摄影", "编程"],
"married": True
}
# 转换为带缩进、有序、保留中文的JSON字符串
json_str = json.dumps(data, ensure_ascii=False, indent=4, sort_keys=True)
print(json_str)
写入JSON文件(json.dump)
dump = dump file object,参数和dumps一致。
import json
data = {
"name": "李四",
"age": 35,
"city": "上海",
"hobbies": ["游泳", "音乐"],
"married": False
}
with open('output.json', 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4, sort_keys=True)
🚀 高级用法
处理自定义类/日期时间对象
标准的json模块无法直接序列化自定义类(如User)或datetime,可以用两种方法:
方法1:自定义default函数(简单灵活)
在dumps/dump时传入default,告诉模块怎么处理不认识的对象:
import json
from datetime import datetime
class User:
def __init__(self, name, age):
self.name = name
self.age = age
self.created_at = datetime.now()
def custom_encoder(obj):
# 先处理特殊类型
if isinstance(obj, datetime):
# 转ISO格式字符串,跨平台且可读
return obj.isoformat()
if isinstance(obj, User):
# 显式指定要序列化的字段(比直接用__dict__安全,不会带临时属性)
return {
"name": obj.name,
"age": obj.age,
"created_at": obj.created_at
}
# 其他类型交给默认处理(会抛出TypeError)
raise TypeError(f"Object of type {obj.__class__.__name__} not serializable")
user = User("王五", 40)
json_str = json.dumps(user, default=custom_encoder, ensure_ascii=False, indent=2)
print(json_str)
方法2:继承json.JSONEncoder(适合封装)
如果多个地方要用到同样的序列化逻辑,可以封装成子类,传入cls参数:
class CustomJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, User):
return {
"name": obj.name,
"age": obj.age,
"created_at": obj.created_at
}
return super().default(obj)
# 调用时传cls
json_str = json.dumps(user, cls=CustomJSONEncoder, ensure_ascii=False, indent=2)
解析时自动还原特殊类型(object_hook)
读取JSON时,每解析到一个JSON对象(即Python的dict),都会调用object_hook函数,我们可以在这里把ISO格式的时间还原成datetime,或者把普通dict还原成User:
import json
from datetime import datetime
def custom_decoder(dct):
# 每解析到一个dict就检查
if "created_at" in dct:
try:
dct["created_at"] = datetime.fromisoformat(dct["created_at"])
except ValueError:
pass # 如果格式不对就保留字符串
# 如果有name和age,还可以还原成User(可选)
if "name" in dct and "age" in dct and "created_at" in dct:
return User(dct["name"], dct["age"]) # 注意这里要把created_at单独处理下
return dct
json_str = '''
{
"name": "赵六",
"age": 45,
"created_at": "2023-01-15T10:30:00"
}
'''
data = json.loads(json_str, object_hook=custom_decoder)
print(type(data)) # <class '__main__.User'>(如果上面做了还原)
print(type(data["created_at"]) if isinstance(data, dict) else type(data.created_at)) # <class 'datetime.datetime'>
流式处理大型JSON文件(用ijson)
如果JSON文件超过内存限制(比如几个G的日志或爬虫数据),可以用第三方库ijson逐块读取,不用一次性加载整个文件:
# 先安装ijson
pip install ijson
import ijson
# 假设large_users.json是一个包含很多用户的数组:[{"name":...}, {"name":...}, ...]
with open('large_users.json', 'rb') as f:
# 只流式读取并处理所有用户名,不用加载整个数组
for name in ijson.items(f, 'item.name'):
print(name)
# 这里可以做插入数据库、统计等操作
✅ 最佳实践
- 编码问题必注意:读写文件加
encoding='utf-8',序列化加ensure_ascii=False
- 文件操作必用
with:自动管理文件句柄,防止泄漏
- 错误处理必加:至少捕获
FileNotFoundError(文件不存在)、json.JSONDecodeError(解析错误,Python3.5+)、TypeError(序列化错误)
- 大型数据必用流式:
ijson、pandas.read_json(chunksize=...)都是好选择
- 安全性必考虑:不要解析来自不可信源的JSON,防止恶意代码注入(虽然Python的
json模块比eval安全,但还是要谨慎)
🎉 总结
Python的标准库json已经能覆盖90%的日常JSON处理需求,搭配ijson等小工具,轻量级文本存储、API交互、临时数据缓存都能轻松搞定~下次碰到这类需求,别再急着开数据库啦!