4、JSON数据处理:JSON语法规则、Python中的json模块、序列化与反序列化、复杂嵌套结构处理
JSON,全称 JavaScript Object Notation。说白了,它就是一套数据交换的通用语言。你想想看,桌面应用要和后端API通信,总得有个双方都认的格式吧?XML太重了,纯文本又太简陋。JSON正好卡在中间——轻量、易读、解析快。
我个人习惯把JSON叫做「结构化文本」。它看起来像Python的字典和列表,但又不完全是。嗯,这里要注意:JSON的语法比Python严格得多。我见过太多新手因为多写了一个逗号,调试了半天。
4.1 JSON语法规则——别踩这些坑
JSON的语法其实很简单,就几条规则。但越是简单的东西,越容易在细节上翻车。我曾经在一个项目里,因为JSON的key忘记加双引号,导致整个数据解析失败。那次排查花了我整整两个小时。
来看核心规则:
- 键必须用双引号——Python里你可以写
{'name': '张三'},但JSON必须写成{"name": "张三"}。单引号?不行。 - 字符串必须用双引号——和上面同理。值如果是字符串,也得双引号包着。
- 不能有尾随逗号——Python里
[1, 2, 3,]是合法的,但JSON里最后一个元素后面不能有逗号。 - 值类型有限制——只支持:字符串、数字、布尔值、null、数组、对象。Python的元组、集合、datetime?统统不支持。
一个合法的JSON示例:
{
"name": "数据采集工具",
"version": 2.1,
"is_active": true,
"features": ["实时监控", "日志导出"],
"config": {
"timeout": 30,
"retry": null
}
}
为什么JSON要这么严格?说白了,就是为了跨语言解析时不出歧义。你想想看,Python的字典和JavaScript的对象长得像,但底层实现完全不同。JSON就是那个「最大公约数」。
4.2 Python中的json模块——你的瑞士军刀
Python标准库自带的 json 模块,我几乎每天都在用。它提供了四个核心函数,两两对应:
| 函数 | 作用 | 输入 | 输出 |
|---|---|---|---|
json.dumps() |
序列化(Python → JSON字符串) | Python对象 | 字符串 |
json.loads() |
反序列化(JSON字符串 → Python) | 字符串 | Python对象 |
json.dump() |
序列化并写入文件 | Python对象 + 文件对象 | 无(直接写入文件) |
json.load() |
从文件读取并反序列化 | 文件对象 | Python对象 |
我建议你记住这个规律:带s的是操作字符串,不带s的是操作文件。dumps的s代表string,loads同理。这样就不会搞混了。
我的小技巧:调试时先用 json.dumps(data, indent=2, ensure_ascii=False)。indent让输出格式化,ensure_ascii=False保证中文不被转义成\uXXXX。这两个参数我几乎每次都加。
4.3 序列化与反序列化——数据变形记
序列化,就是把Python对象变成JSON字符串。反序列化,就是反过来。听起来简单,但实际项目中坑不少。
先看一个典型的序列化场景:
import json
# 一个桌面应用的配置数据
app_config = {
"app_name": "网络监控器",
"version": "1.3.0",
"window_size": [1280, 720],
"is_maximized": False,
"plugins": None
}
# 序列化成JSON字符串
json_str = json.dumps(app_config, indent=2, ensure_ascii=False)
print(json_str)
输出结果:
{
"app_name": "网络监控器",
"version": "1.3.0",
"window_size": [1280, 720],
"is_maximized": false,
"plugins": null
}
注意看,Python的 False 变成了 false,None 变成了 null。这就是序列化的「翻译」过程。
反序列化则相反:
# 从API返回的JSON字符串
api_response = '{"status": "ok", "data": {"count": 42, "items": ["a", "b"]}}'
# 反序列化成Python字典
parsed = json.loads(api_response)
print(parsed["data"]["count"]) # 输出 42
我曾经踩过的坑:反序列化时,JSON的 true 和 false 会变成Python的 True 和 False(注意大小写)。如果你用 if data["flag"] == "true" 去判断,永远不成立。正确写法是 if data["flag"] 或者 if data["flag"] is True。
4.4 复杂嵌套结构处理——实战中的硬骨头
真实项目中的JSON,很少像上面那么规整。我处理过最复杂的一个API返回,嵌套了7层,里面还有数组套对象、对象套数组。嗯,这里分享几个实战技巧。
4.4.1 安全访问嵌套数据
直接 data["key1"]["key2"] 很容易报KeyError。我习惯用 .get() 方法:
# 不安全的写法
# name = data["user"]["profile"]["name"] # 可能崩溃
# 安全的写法
name = data.get("user", {}).get("profile", {}).get("name", "未知用户")
为什么用空字典 {} 作为默认值?因为这样链式调用不会断。你想想看,如果 data.get("user") 返回了None,再调用 .get() 就会报AttributeError。用空字典就安全了。
4.4.2 处理数组中的对象
API经常返回这种结构:
{
"users": [
{"id": 1, "name": "张三", "roles": ["admin"]},
{"id": 2, "name": "李四", "roles": ["editor", "viewer"]},
{"id": 3, "name": "王五", "roles": []}
]
}
要提取所有有admin角色的用户,可以这样:
admins = [user for user in data["users"] if "admin" in user.get("roles", [])]
列表推导式配合 .get(),一行搞定。我经常用这种写法处理批量数据。
4.4.3 自定义序列化——处理特殊类型
Python的datetime、Decimal等类型,json模块默认不认识。这时候需要自定义序列化器:
import json
from datetime import datetime
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
# 可以继续添加其他类型处理
return super().default(obj)
# 使用自定义编码器
data = {
"event": "系统启动",
"time": datetime.now()
}
json_str = json.dumps(data, cls=CustomEncoder, ensure_ascii=False)
print(json_str)
# 输出: {"event": "系统启动", "time": "2025-01-15 14:30:00"}
我的建议:如果项目中频繁处理特殊类型,可以封装一个通用的 to_json() 函数,把常用的类型转换都写进去。这样团队里所有人都用同一个标准,避免格式不统一。
4.5 实战:从API获取并处理嵌套JSON
最后,我们串起来看一个完整的例子。假设我们要从一个天气API获取数据:
import json
import urllib.request
# 模拟API返回的JSON数据
api_response = '''
{
"code": 200,
"message": "success",
"data": {
"city": "北京",
"update_time": "2025-01-15 14:00:00",
"weather": {
"current": {
"temp": -2,
"humidity": 45,
"wind": {"speed": 3.5, "direction": "北风"}
},
"forecast": [
{"date": "01-15", "high": 2, "low": -8, "type": "晴"},
{"date": "01-16", "high": 0, "low": -10, "type": "多云"},
{"date": "01-17", "high": -1, "low": -12, "type": "小雪"}
]
}
}
}
'''
# 反序列化
weather_data = json.loads(api_response)
# 安全提取数据
city = weather_data.get("data", {}).get("city", "未知城市")
current_temp = weather_data.get("data", {}).get("weather", {}).get("current", {}).get("temp", "N/A")
# 提取未来三天最低温度
forecast = weather_data.get("data", {}).get("weather", {}).get("forecast", [])
min_temps = [day.get("low") for day in forecast if day.get("low") is not None]
print(f"城市: {city}")
print(f"当前温度: {current_temp}°C")
print(f"未来三天最低温: {min_temps}")
这个例子涵盖了本节所有知识点:JSON语法、序列化/反序列化、安全访问嵌套结构、列表推导式处理数组。你在实际开发中,遇到的API返回只会比这个更复杂,不会更简单。掌握了这些技巧,至少能应付80%的场景。
记住一句话:处理JSON,安全第一,优雅第二。先保证不崩溃,再考虑代码写得漂不漂亮。我早期做项目时,就因为太追求「一行代码搞定」,结果线上出了好几次事故。从那以后,我宁愿多写几行 .get(),也不冒KeyError的风险。