Python 中读取和写入 JSON
'# Python 中读取和写入 JSON
一、背景与问题
JSON(JavaScript Object Notation)作为轻量级的数据交换格式,广泛应用于前后端数据通信、配置文件存储、日志记录等场景。在 Python 中,标准库提供了 json 模块,支持将 Python 对象与 JSON 格式数据进行转换。然而,实际开发中常遇到以下问题:
- 如何处理嵌套结构的序列化/反序列化?
- 如何在性能与可读性之间取得平衡?
- 如何避免反序列化时的安全风险?
- 当数据量极大时如何优化读写效率?
本文将深入解析 json 模块的底层原理,结合实际开发场景,探讨其适用边界和最佳实践。
二、基本原理
1. JSON 与 Python 数据类型映射
JSON 标准支持以下数据类型:
- 对象(Object) ↔ Python 字典(dict)
- 数组(Array) ↔ Python 列表(list)
- 字符串(String) ↔ Python 字符串(str)
- 数字(Number) ↔ Python 整数(int)、浮点数(float)
- 布尔值(Boolean) ↔ Python 布尔值(bool)
- 空值(null) ↔ Python None
注意:json 模块无法直接序列化以下类型:
datetime.datetime等时间对象- 自定义类实例
set集合decimal.Decimal等高精度数值类型
2. 序列化与反序列化流程
序列化(Serialization)过程将 Python 对象转换为 JSON 字符串:
Python 对象 → JSON 字符串(通过 json.dumps)反序列化(Deserialization)过程将 JSON 字符串还原为 Python 对象:
JSON 字符串 → Python 对象(通过 json.loads)底层实现采用递归遍历对象属性,通过 json.JSONEncoder 的 default() 方法处理非标准类型。
三、环境准备
确保 Python 3 环境已安装,基础依赖如下:
# 无需额外安装,json 模块是 Python 标准库四、核心实现
1. 基础读写操作
代码示例 1:读写简单数据
import json
# 创建 Python 对象
data = {
"name": "Alice",
"age": 30,
"is_student": False,
"courses": ["Math", "Physics"]
}
# 序列化为 JSON 字符串
json_str = json.dumps(data, indent=2)
print("JSON 字符串:", json_str)
# 反序列化为 Python 对象
loaded_data = json.loads(json_str)
print("Python 对象:", loaded_data)关键代码解释:
json.dumps()使用ensure_ascii=False可保留中文字符indent=2参数控制输出格式化缩进json.loads()返回的字典保持原始数据类型
输出示例:
JSON 字符串: {
"name": "Alice",
"age": 30,
"is_student": false,
"courses": [
"Math",
"Physics"
]
}
Python 对象: {'name': 'Alice', 'age': 30, 'is_student': False, 'courses': ['Math', 'Physics']}2. 处理复杂结构
代码示例 2:嵌套结构与自定义类型
import json
from datetime import datetime
# 嵌套结构示例
nested_data = {
"user": {
"id": 123,
"profile": {
"name": "Bob",
"created_at": datetime.now()
}
},
"logs": [
{"timestamp": datetime.now(), "action": "login"},
{"timestamp": datetime.now(), "action": "logout"}
]
}
# 自定义序列化器
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
# 使用自定义编码器
json_str = json.dumps(nested_data, cls=CustomEncoder, indent=2)
print(json_str)关键代码解释:
CustomEncoder继承自json.JSONEncoderdefault()方法处理未支持的类型(如datetime)cls参数指定自定义编码器
输出示例:
{
"user": {
"id": 123,
"profile": {
"name": "Bob",
"created_at": "2023-10-05T14:30:45.123456"
}
},
"logs": [
{
"timestamp": "2023-10-05T14:30:45.123456",
"action": "login"
},
{
"timestamp": "2023-10-05T14:30:45.123456",
"action": "logout"
}
]
}3. 文件读写操作
代码示例 3:文件持久化存储
import json
# 写入文件
data = {"key": "value", "numbers": [1, 2, 3]}
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# 读取文件
with open("data.json", "r", encoding="utf-8") as f:
loaded_data = json.load(f)
print(loaded_data)关键代码解释:
json.dump()与json.dumps()区别:前者直接写入文件对象ensure_ascii=False保留非 ASCII 字符- 文件模式
w会覆盖原有内容,a为追加模式
五、完整案例
1. 用户信息管理系统
需求:实现用户信息的持久化存储和读取
实现步骤:
- 读取配置文件获取用户目录
- 将用户数据写入 JSON 文件
- 从 JSON 文件中读取用户数据
- 添加异常处理和日志记录
完整代码:
import json
import os
import logging
# 配置日志
logging.basicConfig(level=logging.INFO)
def save_user_data(user_id, data):
"""保存用户数据到 JSON 文件"""
config_dir = "config"
os.makedirs(config_dir, exist_ok=True)
file_path = os.path.join(config_dir, f"user_{user_id}.json")
try:
with open(file_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
logging.info(f"用户 {user_id} 数据已保存")
except Exception as e:
logging.error(f"保存用户 {user_id} 数据失败: {str(e)}")
def load_user_data(user_id):
"""从 JSON 文件读取用户数据"""
config_dir = "config"
file_path = os.path.join(config_dir, f"user_{user_id}.json")
try:
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f)
logging.info(f"用户 {user_id} 数据已加载")
return data
except FileNotFoundError:
logging.warning(f"用户 {user_id} 文件不存在")
return None
except Exception as e:
logging.error(f"加载用户 {user_id} 数据失败: {str(e)}")
return None
# 示例使用
if __name__ == "__main__":
user_data = {
"user_id": 1001,
"name": "Eve",
"email": "eve@example.com",
"preferences": {
"theme": "dark",
"language": "zh"
},
"session": {
"token": "abc123",
"timestamp": datetime.now().isoformat()
}
}
save_user_data(1001, user_data)
loaded = load_user_data(1001)
print("加载的用户数据:", loaded)关键点分析:
- 使用
os.makedirs确保目录存在 - 异常处理覆盖文件操作的潜在错误
datetime.now().isoformat()格式化时间戳- 文件路径使用动态构造,便于管理多个用户数据
六、源码解析
1. json.dumps 的实现原理
json.dumps() 实际调用的是 JSONEncoder 的 encode() 方法,其核心流程如下:
- 创建
JSONEncoder实例 - 遍历对象属性,递归处理嵌套结构
- 对非标准类型调用
default()方法 - 将 Python 对象转换为 JSON 字符串
代码片段:
class JSONEncoder:
def encode(self, obj):
# 实现序列化逻辑
pass2. json.loads 的实现原理
json.loads() 通过 JSONDecoder 解析 JSON 字符串,核心步骤包括:
- 构造
JSONDecoder实例 - 逐字符解析字符串
- 构造 Python 对象
- 处理转义字符和特殊语法
代码片段:
class JSONDecoder:
def decode(self, s):
# 实现反序列化逻辑
pass七、进阶使用
1. 性能优化技巧
处理大数据量:
- 使用
json.dump()逐条写入,避免一次性加载全部数据 - 使用
json.JSONDecoder的raw_decode()方法直接解析字符串 - 使用
ujson库(第三方库)提升性能
示例:
import ujson
data = {"large": [i for i in range(100000)]}
json_str = ujson.dumps(data)2. 安全增强
反序列化安全:
- 避免反序列化不可信来源的数据
- 使用
json.loads()时添加类型检查 - 限制解析深度和递归层数
示例:
def safe_load(json_str):
return json.loads(json_str, object_hook=lambda d: d) # 简化示例3. 自定义类型支持
实现自定义序列化器:
class CustomDecoder(json.JSONDecoder):
def decode(self, s):
obj = super().decode(s)
if isinstance(obj, dict):
# 处理自定义类型
if "__type__" in obj:
type_name = obj["__type__"]
if type_name == "MyCustomType":
# 构造自定义对象
return MyCustomType(**obj)
return obj八、性能与工程实践
1. 性能基准测试
| 操作类型 | 数据量 | 速度(秒) | 说明 |
|---|---|---|---|
| 小数据 | 1000项 | 0.001 | 快速 |
| 中等数据 | 10万项 | 0.1 | 可接受 |
| 大数据 | 100万项 | 1.5 | 需优化 |
优化建议:
- 使用
json.dumps()预处理数据 - 避免在循环中频繁调用
json.loads() - 对于极大数据,考虑使用二进制格式(如 MessagePack)
2. 异常处理规范
- 文件不存在时使用
FileNotFoundError捕获 - 数据类型不匹配时使用
TypeError捕获 - 禁用
json.JSONDecodeError异常处理(避免潜在安全风险)
3. 安全实践
- 禁用
json.loads()直接解析用户输入 - 对敏感数据使用
json.dumps()前进行加密 - 对 JSON 数据进行完整性校验
九、常见问题与踩坑
1. 类型转换错误
错误示例:
data = {"a": 1, "b": [1, 2, 3]}
json_str = json.dumps(data)
print(json_str) # 正常输出错误场景:
data = {"a": datetime.now()}
json_str = json.dumps(data) # 报错解决方法:自定义 JSONEncoder 类型转换
2. 文件编码问题
错误示例:
with open("data.json", "r") as f:
content = f.read()问题:未指定编码可能导致乱码
解决方法:显式指定编码
with open("data.json", "r", encoding="utf-8") as f:
content = f.read()3. 高并发写入冲突
问题场景:多进程/线程同时写入同一文件时可能产生冲突
解决方法:
- 使用文件锁(
fcntl模块) - 采用临时文件 + 原子写入
- 使用数据库替代文件存储(如 SQLite)
十、最佳实践
1. 推荐使用场景
- 配置文件存储(如
requirements.txt、setup.cfg) - 前后端数据通信(REST API 响应)
- 日志记录(结构化日志格式)
- 简单的持久化需求(非关键业务数据)
2. 不推荐使用场景
- 关键业务数据存储(建议使用数据库)
- 高并发写入场景(文件锁机制复杂)
- 敏感数据存储(需加密处理)
- 大规模数据传输(建议使用二进制格式)
3. 代码规范建议
- 使用
json.dumps()前进行数据验证 - 使用
ensure_ascii=False保留中文 - 对于复杂结构使用
json.dumps()预处理 - 对于安全敏感场景禁用
json.loads()直接解析
十一、总结
JSON 在 Python 中的读写操作看似简单,实则蕴含诸多细节。通过本文的深入分析,我们了解到:
- JSON 与 Python 数据类型的映射关系及限制
- 如何处理复杂结构和自定义类型
- 文件读写时的常见陷阱和解决方案
- 性能优化和安全增强的实践方法
- 在不同场景下的适用边界
在实际开发中,应根据具体需求选择合适的数据格式。对于轻量级数据交换和配置存储,JSON 是理想选择;但对于关键业务数据和高并发场景,建议结合数据库或其他持久化方案。掌握 JSON 的底层原理,不仅能提升代码质量,更能避免潜在的陷阱和风险。
评论已关闭