🐍JSON 处理 — 数据交换的万能语言
🕐 预计用时:2-3 小时 | 🎯 目标:掌握 JSON 读写、嵌套处理、文件交互、API 数据解析
📖 今日目录
- json.loads() — JSON 字符串 → Python 对象
- json.dumps() — Python 对象 → JSON 字符串
- json.load() / json.dump() — 文件读写
1. JSON 是什么?为什么重要?
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。几乎所有编程语言都支持它,是互联网数据传输的"通用语言"。
你每天都在接触 JSON:
- ⚙️ 配置文件(如 VS Code 的 settings.json)→ JSON
# JSON 长这样:
{
"name": "张三",
"age": 25,
"is_student": false,
"hobbies": ["编程", "游戏", "游泳"],
"address": {
"city": "北京",
"zip": "100000"
},
"spouse": null
}
💡 JSON vs Python 字典:看起来很像,但有区别!JSON 的键必须用双引号,值只能是特定类型(字符串、数字、布尔、null、数组、对象)。
Python 与 JSON 的类型对照
2. json.loads() — JSON 字符串 → Python 对象
json.loads() 把 JSON 格式的字符串转换成 Python 对象(通常是字典)。"loads" = "load string"(加载字符串)。
import json
# 一个 JSON 格式的字符串
json_str = '{"name": "张三", "age": 25, "is_student": false}'
# 转换成 Python 字典
data = json.loads(json_str)
print(data) # {'name': '张三', 'age': 25, 'is_student': False}
print(type(data)) # <class 'dict'>
print(data["name"]) # 张三
print(data["age"]) # 25
# JSON 数组 → Python 列表
json_str = '[1, 2, 3, "hello", true, null]'
result = json.loads(json_str)
print(result) # [1, 2, 3, 'hello', True, None]
print(type(result)) # <class 'list'>
# 嵌套的 JSON
json_str = '''
{
"students": [
{"name": "张三", "score": 95},
{"name": "李四", "score": 88}
],
"class": "三年二班"
}
'''
data = json.loads(json_str)
print(data["class"]) # 三年二班
print(data["students"][0]["name"]) # 张三
print(data["students"][1]["score"]) # 88
⚠️ 常见错误:JSON 字符串必须用双引号!单引号会报错。
json.loads("{'name': '张三'}") → ❌ JSONDecodeError
json.loads('{"name": "张三"}') → ✅ 正确
处理 JSON 解析错误
import json
bad_json = '{"name": "张三", age: 25}' # age 没有双引号
try:
data = json.loads(bad_json)
except json.JSONDecodeError as e:
print(f"JSON 解析失败: {e}")
# JSON 解析失败: Expecting property name enclosed in double quotes: line 1 column 22 (char 21)
3. json.dumps() — Python 对象 → JSON 字符串
json.dumps() 把 Python 对象转换成 JSON 格式的字符串。"dumps" = "dump string"(转储字符串)。
import json
# Python 字典 → JSON 字符串
data = {
"name": "张三",
"age": 25,
"is_student": False,
"hobbies": ["编程", "游戏"],
"address": None
}
json_str = json.dumps(data)
print(json_str)
# {"name": "\u5f20\u4e09", "age": 25, "is_student": false, "hobbies": ["\u7f16\u7a0b", "\u6e38\u620f"], "address": null}
print(type(json_str)) # <class 'str'>
⚠️ 中文被转义了?默认情况下,dumps 会把非 ASCII 字符转义成 \uXXXX。加上 ensure_ascii=False 就能保留中文。
# 保留中文
json_str = json.dumps(data, ensure_ascii=False)
print(json_str)
# {"name": "张三", "age": 25, "is_student": false, "hobbies": ["编程", "游戏"], "address": null}
类型转换对照
import json
# ❌ set 不能直接转 JSON
data = {"tags": {"python", "java", "go"}}
# json.dumps(data) # TypeError: Object of type set is not JSON serializable
# ✅ 先转成 list
data["tags"] = list(data["tags"])
print(json.dumps(data)) # {"tags": ["python", "go", "java"]}
4. json.load() / json.dump() — 文件读写
load() 和 dump()(没有 s)直接操作文件对象,省去手动读写的步骤。
写入 JSON 文件
import json
data = {
"name": "小龙",
"version": "1.0",
"skills": ["Python", "正则", "JSON"],
"config": {
"theme": "dark",
"language": "zh-CN"
}
}
# 写入文件
with open("config.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
print("写入完成!")
# config.json 文件内容:
{
"name": "小龙",
"version": "1.0",
"skills": [
"Python",
"正则",
"JSON"
],
"config": {
"theme": "dark",
"language": "zh-CN"
}
}
读取 JSON 文件
import json
# 从文件读取
with open("config.json", "r", encoding="utf-8") as f:
data = json.load(f)
print(data["name"]) # 小龙
print(data["config"]["theme"]) # dark
print(data["skills"][0]) # Python
💡 load vs loads 的区别:
json.load(f) → 从文件对象读取
json.loads(string) → 从字符串解析
json.dump(data, f) → 写入文件对象
json.dumps(data) → 转成字符串
5. 嵌套结构处理
真实世界的 JSON 往往层层嵌套——学会"钻"进去取数据是核心技能。
import json
# 模拟一个 API 返回的复杂数据
api_response = {
"status": "success",
"data": {
"users": [
{
"id": 1,
"name": "张三",
"contacts": {
"email": "zhangsan@example.com",
"phone": "13800138001"
},
"orders": [
{"order_id": "A001", "amount": 99.9},
{"order_id": "A002", "amount": 199.0}
]
},
{
"id": 2,
"name": "李四",
"contacts": {
"email": "lisi@example.com",
"phone": "13800138002"
},
"orders": [
{"order_id": "B001", "amount": 59.5}
]
}
],
"total": 2
}
}
# 取出张三的邮箱
email = api_response["data"]["users"][0]["contacts"]["email"]
print(email) # zhangsan@example.com
# 取出李四的第一个订单金额
amount = api_response["data"]["users"][1]["orders"][0]["amount"]
print(amount) # 59.5
安全取值:防止 KeyError
# ❌ 直接取值,key 不存在会报错
# value = api_response["data"]["users"][99] # IndexError!
# ✅ 方法1:先判断
if "data" in api_response and "users" in api_response["data"]:
users = api_response["data"]["users"]
print(f"共有 {len(users)} 个用户")
# ✅ 方法2:用 get() 设置默认值
config = json.loads('{"theme": "dark"}')
font_size = config.get("font_size", 16) # 不存在就用默认值 16
print(font_size) # 16
遍历嵌套数据
import json
# 遍历所有用户和订单
for user in api_response["data"]["users"]:
name = user["name"]
email = user["contacts"]["email"]
print(f"\n👤 {name} ({email})")
for order in user["orders"]:
oid = order["order_id"]
amount = order["amount"]
print(f" 📦 订单 {oid}: ¥{amount}")
# 输出:
# 👤 张三 (zhangsan@example.com)
# 📦 订单 A001: ¥99.9
# 📦 订单 A002: ¥199.0
#
# 👤 李四 (lisi@example.com)
# 📦 订单 B001: ¥59.5
6. 类型映射与注意事项
JSON 和 Python 的类型不是完全一一对应的,有几个容易踩的坑。
坑 1:整数键 → 字符串键
import json
# Python 字典用整数做键
data = {1: "一", 2: "二", 3: "三"}
json_str = json.dumps(data)
print(json_str) # {"1": "一", "2": "二", "3": "三"}
# 从 JSON 变回来,键变成字符串了!
result = json.loads(json_str)
print(result) # {'1': '一', '2': '二', '3': '三'}
print(result[1]) # ❌ KeyError: 1
print(result["1"]) # ✅ 一
坑 2:元组 → 数组 → 列表
import json
# Python 元组
data = {"point": (10, 20)}
json_str = json.dumps(data)
print(json_str) # {"point": [10, 20]}
# 读回来变成列表了
result = json.loads(json_str)
print(type(result["point"])) # <class 'list'> 不再是 tuple!
坑 3:None → null → None
import json
data = {"value": None}
json_str = json.dumps(data)
print(json_str) # {"value": null}
result = json.loads(json_str)
print(result["value"]) # None
print(result["value"] is None) # True ✅
坑 4:布尔值的大小写
import json
# JSON: true/false(小写)
# Python: True/False(大写)
data = {"flag": True}
json_str = json.dumps(data)
print(json_str) # {"flag": true} ← JSON 格式是小写
result = json.loads(json_str)
print(result["flag"]) # True ← Python 又变回大写
⚠️ 总结:JSON 不是 Python!
从 JSON 读回来的数据,整数键变字符串、元组变列表、大小写可能变。如果你对类型敏感,读取后要手动转换。
7. 美化输出与排序
调试时,一行 JSON 根本没法看。dumps 的参数可以帮你格式化。
import json
data = {
"name": "张三",
"age": 25,
"city": "北京",
"skills": ["Python", "Java", "Go"]
}
# 默认输出:一行,挤在一起
print(json.dumps(data, ensure_ascii=False))
# {"name": "张三", "age": 25, "city": "北京", "skills": ["Python", "Java", "Go"]}
# indent=2:缩进美化
print(json.dumps(data, ensure_ascii=False, indent=2))
# {
# "name": "张三",
# "age": 25,
# "city": "北京",
# "skills": [
# "Python",
# "Java",
# "Go"
# ]
# }
# sort_keys=True:按键名排序
print(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True))
# {
# "age": 25,
# "city": "北京",
# "name": "张三",
# "skills": [
# "Python",
# "Java",
# "Go"
# ]
# }
分隔符控制
import json
data = {"name": "张三", "age": 25}
# 默认分隔符
print(json.dumps(data, ensure_ascii=False))
# {"name": "张三", "age": 25}
# 自定义分隔符:紧凑模式
print(json.dumps(data, ensure_ascii=False, separators=(",", ":")))
# {"name":"张三","age":25}
# 自定义分隔符:特殊格式
print(json.dumps(data, ensure_ascii=False, separators=(", ", " = ")))
# {"name" = "张三", "age" = 25}
8. 实战:解析 API 数据
这是 JSON 最常见的用途——从网络 API 获取数据并解析。我们模拟一个真实的天气 API 响应。
import json
# 模拟 API 返回的 JSON 数据(实际中用 requests.get() 获取)
api_json = '''
{
"city": "北京",
"update_time": "2024-01-15 10:30:00",
"forecast": [
{
"date": "2024-01-15",
"weather": "晴",
"temp_high": 5,
"temp_low": -3,
"wind": "北风 3级"
},
{
"date": "2024-01-16",
"weather": "多云",
"temp_high": 3,
"temp_low": -5,
"wind": "西北风 4级"
},
{
"date": "2024-01-17",
"weather": "小雪",
"temp_high": 0,
"temp_low": -8,
"wind": "北风 5级"
}
]
}
'''
# 解析 JSON
weather = json.loads(api_json)
# 提取信息
print(f"🏙️ 城市: {weather['city']}")
print(f"🕐 更新: {weather['update_time']}")
print()
for day in weather["forecast"]:
date = day["date"]
desc = day["weather"]
high = day["temp_high"]
low = day["temp_low"]
wind = day["wind"]
# 根据天气选 emoji
emoji = {"晴": "☀️", "多云": "⛅", "小雪": "🌨️"}.get(desc, "🌤️")
print(f"{emoji} {date}: {desc}")
print(f" 🌡️ {low}°C ~ {high}°C 💨 {wind}")
print()
# 输出:
# 🏙️ 城市: 北京
# 🕐 更新: 2024-01-15 10:30:00
#
# ☀️ 2024-01-15: 晴
# 🌡️ -3°C ~ 5°C 💨 北风 3级
#
# ⛅ 2024-01-16: 多云
# 🌡️ -5°C ~ 3°C 💨 西北风 4级
#
# 🌨️ 2024-01-17: 小雪
# 🌡️ -8°C ~ 0°C 💨 北风 5级
配合 requests 请求真实 API
import json
import requests
# 请求一个免费的测试 API
response = requests.get("https://httpbin.org/json")
data = response.json() # 等价于 json.loads(response.text)
print(json.dumps(data, indent=2, ensure_ascii=False))
# {
# "slideshow": {
# "author": "Yours Truly",
# "date": "date of publication",
# "slides": [
# {
# "title": "Wake up to WonderWidgets!",
# "type": "all"
# },
# ...
# ],
# "title": "Sample Slide Show"
# }
# }
# .json() 是 requests 库的便捷方法
# 等价于:data = json.loads(response.text)
💡 requests 的 .json() 方法:
response.json() 内部就是调用 json.loads(response.text),是快捷方式。
9. 实战:配置文件管理器
JSON 非常适合做配置文件——结构清晰、可读性强、语言无关。来写一个完整的配置管理工具。
import json
import os
CONFIG_FILE = "app_config.json"
# 默认配置
DEFAULT_CONFIG = {
"app_name": "我的应用",
"version": "1.0.0",
"debug": False,
"database": {
"host": "localhost",
"port": 3306,
"name": "mydb"
},
"logging": {
"level": "INFO",
"file": "app.log"
},
"allowed_ips": ["127.0.0.1", "192.168.1.0/24"]
}
def load_config():
"""加载配置,不存在则创建默认配置"""
if not os.path.exists(CONFIG_FILE):
save_config(DEFAULT_CONFIG)
print("📝 已创建默认配置文件")
return DEFAULT_CONFIG
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def save_config(config):
"""保存配置到文件"""
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=2)
def get_value(config, key_path, default=None):
"""用点号路径获取值,如 'database.host'"""
keys = key_path.split(".")
current = config
for key in keys:
if isinstance(current, dict) and key in current:
current = current[key]
else:
return default
return current
def set_value(config, key_path, value):
"""用点号路径设置值"""
keys = key_path.split(".")
current = config
for key in keys[:-1]:
if key not in current:
current[key] = {}
current = current[key]
current[keys[-1]] = value
save_config(config)
# ---- 使用示例 ----
config = load_config()
# 读取配置
print(get_value(config, "app_name")) # 我的应用
print(get_value(config, "database.host")) # localhost
print(get_value(config, "database.port")) # 3306
print(get_value(config, "logging.level")) # INFO
print(get_value(config, "not.exist", "无")) # 无
# 修改配置
set_value(config, "database.port", 5432)
set_value(config, "debug", True)
set_value(config, "logging.level", "DEBUG")
# 验证修改
print(get_value(config, "database.port")) # 5432
print(get_value(config, "debug")) # True
💡 点号路径取值(get_value(config, "database.host"))是配置管理的常见模式,比一层层 config["database"]["host"] 更优雅,还能处理 key 不存在的情况。
10. 今日小结
| | |
|---|
json.loads(s) | | |
json.dumps(obj) | | |
json.load(f) | | |
json.dump(obj, f) | | |
核心要点
- ✅ JSON 是数据交换的"通用语言",几乎所有语言都支持
- ✅
loads/dumps 操作字符串,load/dump 操作文件 - ✅ 中文输出要加
ensure_ascii=False - ✅ JSON 的键必须是字符串,值不能有 set/datetime 等特殊类型
- ✅ 从 JSON 读回来:整数键变字符串、元组变列表
- ✅ 嵌套取值用
.get() 防止 KeyError
常用参数速查
# 常用 dumps 参数
json.dumps(data,
ensure_ascii=False, # 保留中文
indent=2, # 缩进美化
sort_keys=True, # 键排序
separators=(",", ":") # 紧凑分隔
)
# 读取时处理异常
try:
data = json.loads(text)
except json.JSONDecodeError as e:
print(f"JSON 格式错误: {e}")
🎯 练习建议:
1. 创建一个 JSON 格式的"个人名片"文件,写入姓名、年龄、技能列表,再读取出来
2. 模拟一个电商 API 响应(商品列表 + 价格),用代码计算总价
3. 写一个函数,支持用 "a.b.c" 格式的路径修改嵌套字典的值
📚 Day28 完成!明天学习 CSV 处理 —— 表格数据的读写利器