一、什么是 JSON?
JSON (JavaScript Object Notation,JavaScript 对象表示法)是一种轻量级的数据交换格式。
1.1 生活比喻
把 JSON 想象成一张快递单:
- 有固定的格式(键值对)
- 任何人都能看懂(跨语言、跨平台)
- 用来在不同系统之间传递信息
比如:淘宝(Python后端)→ 快递公司(JSON数据)→ 你手机上的物流APP(Java前端)
1.2 JSON 长什么样?
python
{
"name": "张三",
"age": 25,
"is_student": false,
"scores": [92, 85, 78],
"address": {
"city": "北京",
"district": "海淀区"
},
"hobbies": ["编程", "读书", "游泳"],
"phone": null
}
1.3 JSON 的数据类型
| JSON 类型 | 示例 | 对应 Python 类型 |
|---|---|---|
| 对象(Object) | {"key": "value"} |
dict(字典) |
| 数组(Array) | [1, 2, 3] |
list(列表) |
| 字符串(String) | "hello" |
str |
| 数字(Number) | 42 或 3.14 |
int 或 float |
| 布尔(Boolean) | true / false |
True / False |
| 空值(Null) | null |
None |
1.4 JSON 的语法规则
python
✅ 键名必须用双引号: "name": "张三"
❌ 不能用单引号: 'name': '张三'
✅ 字符串用双引号: "hello"
❌ 不能用单引号: 'hello'
✅ 布尔值小写: true, false
❌ 不能大写: True, False(这是Python的写法)
✅ 空值是 null: null
❌ 不是 None: None(这是Python的写法)
✅ 最后一个元素后不能有逗号: [1, 2, 3]
❌ 不能有尾逗号: [1, 2, 3,]
✅ 不能有注释: // 这是注释 ← JSON不支持!
1.5 JSON vs Python 字典 对照
python
# Python 字典
data = {
"name": "张三",
"age": 25,
"is_student": False, # Python 用 False
"scores": [92, 85, 78],
"phone": None, # Python 用 None
}
python
// JSON(注意区别!)
{
"name": "张三",
"age": 25,
"is_student": false, // JSON 用 false(小写)
"scores": [92, 85, 78],
"phone": null // JSON 用 null(不是 None)
}
二、Python 的 json 模块
Python 内置了 json 模块,无需安装,直接导入:
python
import json
核心函数一览
| 函数 | 作用 | 方向 |
|---|---|---|
json.loads() |
JSON 字符串 → Python 对象 | 字符串 → 对象 |
json.dumps() |
Python 对象 → JSON 字符串 | 对象 → 字符串 |
json.load() |
JSON 文件 → Python 对象 | 文件 → 对象 |
json.dump() |
Python 对象 → JSON 文件 | 对象 → 文件 |
📌 记忆技巧 :带
s的是处理字符串 (string),不带s的是处理文件。
python
loads = load + string(从字符串加载)
dumps = dump + string(导出为字符串)
load = 从文件加载
dump = 导出到文件
三、json.loads() ------ 解析 JSON 字符串
3.1 最简单的例子
python
import json
# 一个 JSON 格式的字符串
json_string = '{"name": "张三", "age": 25, "city": "北京"}'
# 解析:JSON字符串 → Python字典
data = json.loads(json_string)
# 现在 data 就是一个普通的 Python 字典了!
print(type(data)) # <class 'dict'>
print(data["name"]) # 张三
print(data["age"]) # 25
print(data["city"]) # 北京
# 可以像操作字典一样操作
data["age"] = 26 # 修改
data["email"] = "zhangsan@example.com" # 添加
print(data)
# {'name': '张三', 'age': 26, 'city': '北京', 'email': 'zhangsan@example.com'}
3.2 解析各种 JSON 类型
python
import json
# ============ JSON 对象 → Python 字典 ============
obj = json.loads('{"name": "Alice", "age": 30}')
print(type(obj)) # <class 'dict'>
print(obj) # {'name': 'Alice', 'age': 30}
# ============ JSON 数组 → Python 列表 ============
arr = json.loads('[1, 2, 3, "hello", true, null]')
print(type(arr)) # <class 'list'>
print(arr) # [1, 2, 3, 'hello', True, None]
# 注意:true → True,null → None(自动转换!)
# ============ JSON 字符串 → Python 字符串 ============
s = json.loads('"hello world"')
print(type(s)) # <class 'str'>
print(s) # hello world
# ============ JSON 数字 → Python 数字 ============
n1 = json.loads('42')
print(type(n1)) # <class 'int'>
n2 = json.loads('3.14')
print(type(n2)) # <class 'float'>
# ============ JSON 布尔 → Python 布尔 ============
b = json.loads('true')
print(type(b)) # <class 'bool'>
print(b) # True
# ============ JSON null → Python None ============
none_val = json.loads('null')
print(type(none_val)) # <class 'NoneType'>
print(none_val) # None
3.3 解析嵌套的复杂 JSON
python
import json
# 一个复杂的 JSON(模拟电商订单)
json_string = '''
{
"order_id": "ORD-2026-001",
"customer": {
"name": "李四",
"phone": "13800138000",
"address": {
"province": "广东省",
"city": "深圳市",
"district": "南山区",
"street": "科技园路1号"
}
},
"items": [
{
"product": "机械键盘",
"price": 299.00,
"quantity": 1,
"specs": {"轴体": "红轴", "配列": "87键"}
},
{
"product": "鼠标垫",
"price": 49.90,
"quantity": 2,
"specs": {"尺寸": "800x300mm", "材质": "布面"}
}
],
"total_amount": 398.80,
"is_paid": true,
"coupon": null,
"tags": ["电子产品", "办公用品"]
}
'''
# 解析
order = json.loads(json_string)
# ============ 逐层访问数据 ============
# 第一层:直接访问
print(f"订单号:{order['order_id']}")
print(f"总金额:¥{order['total_amount']}")
print(f"是否已付:{order['is_paid']}")
print(f"优惠券:{order['coupon']}") # None
# 第二层:访问嵌套对象
print(f"\n客户姓名:{order['customer']['name']}")
print(f"客户电话:{order['customer']['phone']}")
# 第三层:访问更深的嵌套
addr = order['customer']['address']
print(f"收货地址:{addr['province']}{addr['city']}{addr['district']}{addr['street']}")
# 访问数组
print(f"\n标签:{order['tags']}")
print(f"第一个标签:{order['tags'][0]}")
# 遍历商品列表
print("\n=== 商品清单 ===")
for item in order['items']:
print(f" 📦 {item['product']}")
print(f" 单价:¥{item['price']},数量:{item['quantity']}")
print(f" 规格:{item['specs']}")
subtotal = item['price'] * item['quantity']
print(f" 小计:¥{subtotal:.2f}")
输出:
python
订单号:ORD-2026-001
总金额:¥398.8
是否已付:True
优惠券:None
客户姓名:李四
客户电话:13800138000
收货地址:广东省深圳市南山区科技园路1号
标签:['电子产品', '办公用品']
第一个标签:电子产品
=== 商品清单 ===
📦 机械键盘
单价:¥299.0,数量:1
规格:{'轴体': '红轴', '配列': '87键'}
小计:¥299.00
📦 鼠标垫
单价:¥49.9,数量:2
规格:{'尺寸': '800x300mm', '材质': '布面'}
小计:¥99.80
四、json.dumps() ------ Python 对象转 JSON 字符串
4.1 基本用法
python
import json
# Python 字典
data = {
"name": "王五",
"age": 30,
"is_vip": True, # Python 的 True
"balance": None, # Python 的 None
"scores": [95, 88, 72]
}
# 转换:Python对象 → JSON字符串
json_string = json.dumps(data)
print(json_string)
# {"name": "\u738b\u4e94", "age": 30, "is_vip": true, "balance": null, "scores": [95, 88, 72]}
# 注意:
# True → true(自动转换)
# None → null(自动转换)
# 中文变成了 \uXXXX(Unicode转义)
4.2 常用参数
python
import json
data = {
"name": "王五",
"age": 30,
"scores": {"数学": 95, "英语": 88},
"hobbies": ["编程", "游泳"]
}
# ============ ensure_ascii=False:让中文正常显示 ============
# 默认 ensure_ascii=True,中文会被转义为 \uXXXX
print("默认(转义):")
print(json.dumps(data))
# {"name": "\u738b\u4e94", ...}
print("\nensure_ascii=False(中文可读):")
print(json.dumps(data, ensure_ascii=False))
# {"name": "王五", "age": 30, "scores": {"数学": 95, "英语": 88}, "hobbies": ["编程", "游泳"]}
# ============ indent:格式化缩进(美化输出) ============
print("\n格式化输出(indent=2):")
print(json.dumps(data, ensure_ascii=False, indent=2))
格式化输出效果:
python
{
"name": "王五",
"age": 30,
"scores": {
"数学": 95,
"英语": 88
},
"hobbies": [
"编程",
"游泳"
]
}
python
# ============ sort_keys:按键名排序 ============
print(json.dumps(data, ensure_ascii=False, indent=2, sort_keys=True))
# 键会按字母顺序排列:age, hobbies, name, scores
# ============ separators:自定义分隔符(压缩输出) ============
# 默认分隔符是 ", " 和 ": "(有空格)
# 压缩后去掉空格,减小体积
compact = json.dumps(data, ensure_ascii=False, separators=(",", ":"))
print(compact)
# {"name":"王五","age":30,"scores":{"数学":95,"英语":88},"hobbies":["编程","游泳"]}
# ============ default:处理不能序列化的对象 ============
from datetime import datetime
data_with_date = {
"event": "会议",
"time": datetime(2026, 7, 29, 14, 30) # datetime 不能直接序列化!
}
# ❌ 这样会报错:
# json.dumps(data_with_date) # TypeError: Object of type datetime is not JSON serializable
# ✅ 用 default 参数指定转换方式:
def custom_serializer(obj):
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
raise TypeError(f"无法序列化 {type(obj)}")
result = json.dumps(data_with_date, default=custom_serializer, ensure_ascii=False)
print(result)
# {"event": "会议", "time": "2026-07-29 14:30:00"}
4.3 dumps 参数总结
| 参数 | 类型 | 默认值 | 作用 |
|---|---|---|---|
ensure_ascii |
bool | True |
True:非ASCII字符转义为\uXXXX;False:原样输出 |
indent |
int/str | None |
缩进空格数(None=不缩进,一行输出) |
sort_keys |
bool | False |
是否按键名排序 |
separators |
tuple | (", ", ": ") |
自定义分隔符 |
default |
function | None |
处理不可序列化对象的函数 |
五、json.load() 和 json.dump() ------ 文件读写
5.1 从文件读取 JSON(json.load)
假设有一个文件 config.json:
python
{
"app_name": "我的应用",
"version": "2.1.0",
"database": {
"host": "localhost",
"port": 3306,
"username": "admin",
"password": "secret123"
},
"features": {
"dark_mode": true,
"notifications": true,
"auto_update": false
},
"max_connections": 100
}
python
import json
# ============ 读取 JSON 文件 ============
with open("config.json", "r", encoding="utf-8") as f:
config = json.load(f) # 注意:是 load,不是 loads!
# 现在 config 就是一个 Python 字典
print(f"应用名:{config['app_name']}")
print(f"版本:{config['version']}")
print(f"数据库地址:{config['database']['host']}:{config['database']['port']}")
print(f"暗黑模式:{config['features']['dark_mode']}")
# 修改配置
config['max_connections'] = 200
config['features']['auto_update'] = True
5.2 写入 JSON 到文件(json.dump)
python
import json
# 准备数据
config = {
"app_name": "我的应用",
"version": "2.2.0",
"database": {
"host": "192.168.1.100",
"port": 5432,
"username": "admin",
"password": "new_password"
},
"features": {
"dark_mode": True,
"notifications": True,
"auto_update": True
},
"max_connections": 200
}
# ============ 写入文件 ============
with open("config_new.json", "w", encoding="utf-8") as f:
json.dump(
config,
f,
ensure_ascii=False, # 中文不转义
indent=4, # 4空格缩进(美观)
sort_keys=False # 不排序(保持原始顺序)
)
print("✅ 配置已保存!")
生成的文件内容:
python
{
"app_name": "我的应用",
"version": "2.2.0",
"database": {
"host": "192.168.1.100",
"port": 5432,
"username": "admin",
"password": "new_password"
},
"features": {
"dark_mode": true,
"notifications": true,
"auto_update": true
},
"max_connections": 200
}
5.3 读取 JSON 数组文件
假设 students.json:
python
[
{"id": 1, "name": "张三", "score": 92},
{"id": 2, "name": "李四", "score": 85},
{"id": 3, "name": "王五", "score": 78}
]
python
import json
with open("students.json", "r", encoding="utf-8") as f:
students = json.load(f) # 结果是 Python 列表
print(type(students)) # <class 'list'>
print(f"共 {len(students)} 个学生\n")
for s in students:
print(f" {s['id']}. {s['name']} - {s['score']}分")
# 计算平均分
avg = sum(s['score'] for s in students) / len(students)
print(f"\n平均分:{avg:.1f}")
六、处理网络 API 返回的 JSON
这是 JSON 最常见的实际用途!
6.1 使用 urllib(标准库)
python
import json
import urllib.request
# 调用一个免费的 API(获取随机用户信息)
url = "https://randomuser.me/api/"
# 发送请求
response = urllib.request.urlopen(url)
json_string = response.read().decode("utf-8")
# 解析 JSON
data = json.loads(json_string)
# 提取数据
user = data["results"][0]
name = user["name"]
print(f"姓名:{name['first']} {name['last']}")
print(f"邮箱:{user['email']}")
print(f"城市:{user['location']['city']}")
print(f"国家:{user['location']['country']}")
print(f"头像:{user['picture']['large']}")
6.2 使用 requests(第三方库,更推荐)
python
# 先安装:pip install requests
import requests
# ============ GET 请求 ============
response = requests.get("https://api.github.com/users/octocat")
# requests 自带 .json() 方法,直接解析!
data = response.json()
print(f"用户名:{data['login']}")
print(f"昵称:{data['name']}")
print(f"仓库数:{data['public_repos']}")
print(f"粉丝数:{data['followers']}")
print(f"简介:{data['bio']}")
# ============ 带参数的请求 ============
params = {"q": "python", "sort": "stars", "per_page": 5}
response = requests.get("https://api.github.com/search/repositories", params=params)
data = response.json()
print(f"\n搜索到 {data['total_count']} 个仓库,显示前5个:")
for repo in data['items']:
print(f" ⭐ {repo['full_name']} - {repo['stargazers_count']} stars")
print(f" {repo['description']}")
6.3 发送 JSON 数据(POST 请求)
python
import requests
import json
# 要发送的数据
payload = {
"title": "我的文章",
"body": "这是文章内容...",
"userId": 1
}
# 发送 POST 请求
response = requests.post(
"https://jsonplaceholder.typicode.com/posts",
json=payload # requests 会自动序列化为 JSON 并设置 Content-Type
)
# 查看响应
result = response.json()
print(f"状态码:{response.status_code}")
print(f"创建的帖子ID:{result['id']}")
print(f"响应内容:{json.dumps(result, ensure_ascii=False, indent=2)}")
七、处理复杂的嵌套数据
7.1 安全地访问深层嵌套
python
import json
# 模拟一个复杂的 API 响应
api_response = '''
{
"status": "success",
"data": {
"users": [
{
"id": 1,
"profile": {
"name": "张三",
"contacts": {
"email": "zhang@example.com",
"phones": ["13800138000", "13900139000"]
}
}
},
{
"id": 2,
"profile": {
"name": "李四",
"contacts": null
}
}
]
}
}
'''
data = json.loads(api_response)
# ❌ 危险写法(如果中间某层不存在会报 KeyError)
# email = data["data"]["users"][1]["profile"]["contacts"]["email"]
# 如果 contacts 是 null,这里会报错!
# ✅ 安全写法1:逐层检查
users = data.get("data", {}).get("users", [])
for user in users:
profile = user.get("profile", {})
contacts = profile.get("contacts") # 可能是 None
name = profile.get("name", "未知")
if contacts:
email = contacts.get("email", "无邮箱")
phones = contacts.get("phones", [])
print(f" 👤 {name}:{email},电话:{phones}")
else:
print(f" 👤 {name}:无联系方式")
# ✅ 安全写法2:封装一个安全取值函数
def safe_get(data, *keys, default=None):
"""
安全地获取嵌套字典的值
用法:safe_get(data, "data", "users", 0, "profile", "name")
"""
current = data
for key in keys:
if isinstance(current, dict):
current = current.get(key)
elif isinstance(current, (list, tuple)) and isinstance(key, int):
try:
current = current[key]
except IndexError:
return default
else:
return default
if current is None:
return default
return current
# 使用
email1 = safe_get(data, "data", "users", 0, "profile", "contacts", "email")
print(f"\n用户1邮箱:{email1}") # zhang@example.com
email2 = safe_get(data, "data", "users", 1, "profile", "contacts", "email", default="无")
print(f"用户2邮箱:{email2}") # 无
phone = safe_get(data, "data", "users", 0, "profile", "contacts", "phones", 0)
print(f"用户1第一个电话:{phone}") # 13800138000
7.2 递归遍历 JSON 结构
python
import json
def explore_json(data, indent=0):
"""递归打印 JSON 结构(用于调试/了解数据结构)"""
prefix = " " * indent
if isinstance(data, dict):
for key, value in data.items():
if isinstance(value, (dict, list)):
print(f"{prefix}📁 {key}:")
explore_json(value, indent + 1)
else:
print(f"{prefix}🔹 {key}: {value} ({type(value).__name__})")
elif isinstance(data, list):
for i, item in enumerate(data):
if isinstance(item, (dict, list)):
print(f"{prefix}📂 [{i}]:")
explore_json(item, indent + 1)
else:
print(f"{prefix}🔸 [{i}]: {item} ({type(item).__name__})")
# 测试
json_str = '''
{
"company": "科技公司",
"departments": [
{
"name": "技术部",
"employees": [
{"name": "张三", "skills": ["Python", "Java"]},
{"name": "李四", "skills": ["Go", "Rust"]}
]
},
{
"name": "市场部",
"employees": [
{"name": "王五", "skills": ["营销", "设计"]}
]
}
],
"founded": 2020,
"is_listed": false
}
'''
data = json.loads(json_str)
explore_json(data)
输出:
python
🔹 company: 科技公司 (str)
📁 departments:
📂 [0]:
🔹 name: 技术部 (str)
📁 employees:
📂 [0]:
🔹 name: 张三 (str)
📁 skills:
🔸 [0]: Python (str)
🔸 [1]: Java (str)
📂 [1]:
🔹 name: 李四 (str)
📁 skills:
🔸 [0]: Go (str)
🔸 [1]: Rust (str)
📂 [1]:
🔹 name: 市场部 (str)
📁 employees:
📂 [0]:
🔹 name: 王五 (str)
📁 skills:
🔸 [0]: 营销 (str)
🔸 [1]: 设计 (str)
🔹 founded: 2020 (int)
🔹 is_listed: False (bool)
八、JSON 与 Python 对象的相互转换(高级)
8.1 自定义类的序列化
python
import json
from datetime import datetime
class User:
def __init__(self, name, age, email, created_at):
self.name = name
self.age = age
self.email = email
self.created_at = created_at
def __repr__(self):
return f"User(name={self.name!r}, age={self.age})"
# 创建对象
user = User("张三", 25, "zhang@example.com", datetime(2025, 1, 15))
# ❌ 直接序列化会报错
# json.dumps(user) # TypeError!
# ✅ 方法1:用 default 参数
def user_serializer(obj):
if isinstance(obj, User):
return {
"name": obj.name,
"age": obj.age,
"email": obj.email,
"created_at": obj.created_at.strftime("%Y-%m-%d"),
"_type": "User" # 标记类型,方便反序列化
}
if isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
raise TypeError(f"无法序列化 {type(obj)}")
json_str = json.dumps(user, default=user_serializer, ensure_ascii=False, indent=2)
print(json_str)
输出:
python
{
"name": "张三",
"age": 25,
"email": "zhang@example.com",
"created_at": "2025-01-15",
"_type": "User"
}
python
# ✅ 方法2:让类自己实现序列化(更优雅)
class User:
def __init__(self, name, age, email, created_at):
self.name = name
self.age = age
self.email = email
self.created_at = created_at
def to_dict(self):
"""转为字典(可被 JSON 序列化)"""
return {
"name": self.name,
"age": self.age,
"email": self.email,
"created_at": self.created_at.strftime("%Y-%m-%d")
}
@classmethod
def from_dict(cls, data):
"""从字典创建对象(反序列化)"""
return cls(
name=data["name"],
age=data["age"],
email=data["email"],
created_at=datetime.strptime(data["created_at"], "%Y-%m-%d")
)
# 序列化
user = User("李四", 30, "li@example.com", datetime(2024, 6, 1))
json_str = json.dumps(user.to_dict(), ensure_ascii=False, indent=2)
print("序列化:", json_str)
# 反序列化
data = json.loads(json_str)
user2 = User.from_dict(data)
print(f"反序列化:{user2.name}, {user2.age}岁, 注册于{user2.created_at}")
8.2 使用 object_hook 自动反序列化
python
import json
from datetime import datetime
class Product:
def __init__(self, name, price, in_stock):
self.name = name
self.price = price
self.in_stock = in_stock
def __repr__(self):
return f"Product({self.name!r}, ¥{self.price})"
json_str = '''
[
{"_type": "Product", "name": "键盘", "price": 299, "in_stock": true},
{"_type": "Product", "name": "鼠标", "price": 149, "in_stock": false}
]
'''
def product_decoder(dct):
"""自定义解码器:遇到 _type=Product 就创建 Product 对象"""
if dct.get("_type") == "Product":
return Product(
name=dct["name"],
price=dct["price"],
in_stock=dct["in_stock"]
)
return dct
# 使用 object_hook
products = json.loads(json_str, object_hook=product_decoder)
for p in products:
print(f" {p} - {'有货' if p.in_stock else '缺货'}")
# 输出:
# Product('键盘', ¥299) - 有货
# Product('鼠标', ¥149) - 缺货
九、处理 JSON Lines(.jsonl)格式
9.1 什么是 JSON Lines?
每行一个独立的 JSON 对象,适合处理大量数据(日志、大数据集)。
python
{"name": "张三", "age": 25}
{"name": "李四", "age": 30}
{"name": "王五", "age": 28}
9.2 读取 JSONL
python
import json
def read_jsonl(filepath):
"""逐行读取 JSONL 文件(内存友好,适合大文件)"""
results = []
with open(filepath, "r", encoding="utf-8") as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line: # 跳过空行
continue
try:
data = json.loads(line)
results.append(data)
except json.JSONDecodeError as e:
print(f"⚠️ 第{line_num}行解析失败:{e}")
return results
# 使用
records = read_jsonl("data.jsonl")
for r in records:
print(f" {r['name']} - {r['age']}岁")
9.3 写入 JSONL
python
import json
def write_jsonl(data_list, filepath):
"""将列表写入 JSONL 文件"""
with open(filepath, "w", encoding="utf-8") as f:
for item in data_list:
line = json.dumps(item, ensure_ascii=False)
f.write(line + "\n") # 每行一个 JSON + 换行符
# 使用
data = [
{"event": "login", "user": "张三", "time": "2026-07-29 08:00"},
{"event": "purchase", "user": "李四", "time": "2026-07-29 09:30", "amount": 299},
{"event": "logout", "user": "张三", "time": "2026-07-29 18:00"},
]
write_jsonl(data, "events.jsonl")
print("✅ JSONL 文件已生成")
十、错误处理
10.1 常见错误及解决
python
import json
# ============ 错误1:JSON 格式不正确 ============
bad_json = "{'name': '张三'}" # ❌ 用了单引号!JSON必须用双引号
try:
data = json.loads(bad_json)
except json.JSONDecodeError as e:
print(f"❌ 解析错误:{e}")
print(f" 位置:第{e.lineno}行,第{e.colno}列")
# 输出:❌ 解析错误:Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
# ============ 错误2:尾部多余逗号 ============
bad_json2 = '{"name": "张三", "age": 25,}' # ❌ 最后一个元素后有逗号
try:
data = json.loads(bad_json2)
except json.JSONDecodeError as e:
print(f"❌ 解析错误:{e}")
# ============ 错误3:注释(JSON不支持注释!) ============
bad_json3 = '''
{
"name": "张三", // 这是注释 ← JSON不允许!
"age": 25
}
'''
try:
data = json.loads(bad_json3)
except json.JSONDecodeError as e:
print(f"❌ 解析错误:{e}")
# ============ 错误4:文件编码问题 ============
try:
with open("data.json", "r", encoding="utf-8") as f:
data = json.load(f)
except UnicodeDecodeError:
# 尝试其他编码
with open("data.json", "r", encoding="gbk") as f:
data = json.load(f)
except FileNotFoundError:
print("❌ 文件不存在")
10.2 健壮的 JSON 解析函数
python
import json
import os
def safe_load_json(filepath, default=None):
"""
安全地加载 JSON 文件
参数:
filepath: 文件路径
default: 解析失败时返回的默认值
返回:
解析后的数据,或 default
"""
# 检查文件是否存在
if not os.path.exists(filepath):
print(f"⚠️ 文件不存在:{filepath}")
return default
# 检查文件是否为空
if os.path.getsize(filepath) == 0:
print(f"⚠️ 文件为空:{filepath}")
return default
try:
with open(filepath, "r", encoding="utf-8") as f:
return json.load(f)
except json.JSONDecodeError as e:
print(f"❌ JSON 格式错误({filepath}):{e}")
return default
except UnicodeDecodeError:
# 尝试 GBK 编码
try:
with open(filepath, "r", encoding="gbk") as f:
return json.load(f)
except Exception as e:
print(f"❌ 编码错误:{e}")
return default
except Exception as e:
print(f"❌ 未知错误:{e}")
return default
# 使用
config = safe_load_json("config.json", default={})
if config:
print(f"加载成功:{config.get('app_name', '未知应用')}")
else:
print("使用默认配置")
十一、实战案例
11.1 简易 TODO 应用(JSON 做数据存储)
python
import json
import os
from datetime import datetime
TODO_FILE = "todos.json"
def load_todos():
"""加载待办事项"""
if not os.path.exists(TODO_FILE):
return []
with open(TODO_FILE, "r", encoding="utf-8") as f:
return json.load(f)
def save_todos(todos):
"""保存待办事项"""
with open(TODO_FILE, "w", encoding="utf-8") as f:
json.dump(todos, f, ensure_ascii=False, indent=2)
def add_todo(title, priority="中"):
"""添加待办"""
todos = load_todos()
todo = {
"id": len(todos) + 1,
"title": title,
"priority": priority,
"done": False,
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
"completed_at": None
}
todos.append(todo)
save_todos(todos)
print(f"✅ 已添加:{title}")
def complete_todo(todo_id):
"""完成待办"""
todos = load_todos()
for todo in todos:
if todo["id"] == todo_id:
todo["done"] = True
todo["completed_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
save_todos(todos)
print(f"🎉 已完成:{todo['title']}")
return
print(f"❌ 找不到 ID={todo_id} 的待办")
def show_todos():
"""显示所有待办"""
todos = load_todos()
if not todos:
print("📭 暂无待办事项")
return
print("\n📋 待办事项清单:")
print("-" * 50)
for todo in todos:
status = "✅" if todo["done"] else "⬜"
priority_icon = {"高": "🔴", "中": "🟡", "低": "🟢"}.get(todo["priority"], "⚪")
print(f" {status} [{todo['id']}] {priority_icon} {todo['title']}")
if todo["done"]:
print(f" 完成于:{todo['completed_at']}")
print("-" * 50)
done_count = sum(1 for t in todos if t["done"])
print(f" 进度:{done_count}/{len(todos)}")
# ============ 使用 ============
add_todo("学习 Python JSON 模块", "高")
add_todo("写周报", "中")
add_todo("买咖啡豆", "低")
complete_todo(1)
show_todos()
输出:
python
✅ 已添加:学习 Python JSON 模块
✅ 已添加:写周报
✅ 已添加:买咖啡豆
🎉 已完成:学习 Python JSON 模块
📋 待办事项清单:
--------------------------------------------------
✅ [1] 🔴 学习 Python JSON 模块
完成于:2026-07-29 11:21
⬜ [2] 🟡 写周报
⬜ [3] 🟢 买咖啡豆
--------------------------------------------------
进度:1/3
11.2 爬取数据并保存为 JSON
python
import json
import urllib.request
import time
def fetch_and_save(urls, output_file):
"""批量抓取网页标题并保存为 JSON"""
results = []
for i, url in enumerate(urls, 1):
print(f" [{i}/{len(urls)}] 抓取:{url}")
try:
req = urllib.request.Request(url, headers={
"User-Agent": "Mozilla/5.0 (Python Script)"
})
response = urllib.request.urlopen(req, timeout=10)
html = response.read().decode("utf-8", errors="ignore")
# 简单提取 title(实际项目建议用 BeautifulSoup)
title = ""
if "<title>" in html and "</title>" in html:
start = html.index("<title>") + 7
end = html.index("</title>")
title = html[start:end].strip()
results.append({
"url": url,
"title": title,
"status": response.status,
"fetched_at": time.strftime("%Y-%m-%d %H:%M:%S")
})
except Exception as e:
results.append({
"url": url,
"title": None,
"error": str(e),
"fetched_at": time.strftime("%Y-%m-%d %H:%M:%S")
})
time.sleep(1) # 礼貌等待,别太快
# 保存为 JSON
with open(output_file, "w", encoding="utf-8") as f:
json.dump(results, f, ensure_ascii=False, indent=2)
print(f"\n✅ 结果已保存到 {output_file}")
# 打印摘要
success = sum(1 for r in results if "error" not in r)
print(f" 成功:{success}/{len(urls)}")
# 使用
urls = [
"https://www.python.org",
"https://www.baidu.com",
"https://github.com",
]
fetch_and_save(urls, "scraped_data.json")
11.3 JSON 配置文件管理器
python
import json
import os
class ConfigManager:
"""JSON 配置文件管理器"""
def __init__(self, filepath="config.json"):
self.filepath = filepath
self.config = self._load()
def _load(self):
"""加载配置"""
if os.path.exists(self.filepath):
with open(self.filepath, "r", encoding="utf-8") as f:
return json.load(f)
return {}
def save(self):
"""保存配置"""
with open(self.filepath, "w", encoding="utf-8") as f:
json.dump(self.config, f, ensure_ascii=False, indent=2)
def get(self, key, default=None):
"""获取配置值(支持点号路径,如 'database.host')"""
keys = key.split(".")
value = self.config
for k in keys:
if isinstance(value, dict):
value = value.get(k)
else:
return default
if value is None:
return default
return value
def set(self, key, value):
"""设置配置值(支持点号路径)"""
keys = key.split(".")
config = self.config
# 逐层创建嵌套字典
for k in keys[:-1]:
if k not in config or not isinstance(config[k], dict):
config[k] = {}
config = config[k]
config[keys[-1]] = value
self.save()
def delete(self, key):
"""删除配置项"""
keys = key.split(".")
config = self.config
for k in keys[:-1]:
if k not in config:
return False
config = config[k]
if keys[-1] in config:
del config[keys[-1]]
self.save()
return True
return False
def show(self):
"""打印所有配置"""
print(json.dumps(self.config, ensure_ascii=False, indent=2))
# ============ 使用 ============
cfg = ConfigManager("app_config.json")
# 设置配置(自动创建嵌套结构)
cfg.set("app.name", "我的应用")
cfg.set("app.version", "1.0.0")
cfg.set("database.host", "localhost")
cfg.set("database.port", 3306)
cfg.set("database.credentials.username", "admin")
cfg.set("database.credentials.password", "secret")
cfg.set("features.dark_mode", True)
# 读取配置
print(f"应用名:{cfg.get('app.name')}")
print(f"数据库:{cfg.get('database.host')}:{cfg.get('database.port')}")
print(f"用户名:{cfg.get('database.credentials.username')}")
print(f"不存在的键:{cfg.get('xxx.yyy', '默认值')}")
# 显示全部
print("\n完整配置:")
cfg.show()
十二、第三方 JSON 库(进阶)
12.1 orjson(超高性能)
python
pip install orjson
python
import orjson
from datetime import datetime
import numpy as np
# orjson 比标准 json 快 3~10 倍!
data = {
"name": "测试",
"time": datetime.now(), # 自动处理 datetime!
"numbers": [1, 2, 3],
}
# 序列化(返回 bytes,不是 str)
json_bytes = orjson.dumps(data, option=orjson.OPT_INDENT_2)
print(json_bytes.decode("utf-8"))
# 反序列化
parsed = orjson.loads(json_bytes)
print(parsed["name"])
# 甚至支持 numpy 数组!
arr = np.array([1.0, 2.0, 3.0])
json_bytes = orjson.dumps({"array": arr})
print(json_bytes) # {"array":[1.0,2.0,3.0]}
12.2 各库性能对比
python
import json
import time
# 生成测试数据
data = [{"id": i, "name": f"user_{i}", "score": i * 1.5} for i in range(100000)]
# 标准 json
start = time.time()
s = json.dumps(data)
json_dumps_time = time.time() - start
start = time.time()
json.loads(s)
json_loads_time = time.time() - start
print(f"标准 json: dumps={json_dumps_time:.3f}s, loads={json_loads_time:.3f}s")
# orjson(如果安装了)
try:
import orjson
start = time.time()
s = orjson.dumps(data)
orjson_dumps_time = time.time() - start
start = time.time()
orjson.loads(s)
orjson_loads_time = time.time() - start
print(f"orjson: dumps={orjson_dumps_time:.3f}s, loads={orjson_loads_time:.3f}s")
print(f"加速比: dumps {json_dumps_time/orjson_dumps_time:.1f}x, loads {json_loads_time/orjson_loads_time:.1f}x")
except ImportError:
print("orjson 未安装")
十三、JSON vs XML vs YAML 对比
| 特性 | JSON | XML | YAML |
|---|---|---|---|
| 可读性 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 简洁性 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 解析速度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| 数据类型 | 6种 | 只有字符串 | 丰富 |
| 注释支持 | ❌ | ✅ | ✅ |
| 浏览器原生支持 | ✅ | ✅ | ❌ |
| API 数据交换 | ✅ 主流 | 较少 | 配置文件 |
| Python 内置支持 | ✅ | ✅ | ❌(需PyYAML) |
选择建议:
python
API 数据交换、前后端通信 → JSON ✅
配置文件(人类频繁编辑) → YAML
需要注释、复杂结构、企业级 → XML
十四、常见问题 FAQ
Q1:json.loads() 和 eval() 有什么区别?
python
# ❌ 千万不要用 eval() 解析 JSON!
import json
json_str = '{"name": "张三"}'
# eval 会执行任何 Python 代码(极度危险!)
# eval('__import__("os").system("rm -rf /")') # 这会删除你的文件!
# json.loads 只解析 JSON,安全!
data = json.loads(json_str) # ✅ 安全
Q2:如何处理 NaN 和 Infinity?
python
import json
# Python 的 float('nan') 和 float('inf') 不是合法 JSON
data = {"value": float('nan'), "max": float('inf')}
# 默认会生成不标准的 JSON
print(json.dumps(data)) # {"value": NaN, "max": Infinity} ← 不标准!
# 严格模式(拒绝非标准值)
try:
json.dumps(data, allow_nan=False)
except ValueError as e:
print(f"❌ {e}") # Out of range float values are not JSON compliant
# 解决方案:替换为 null 或字符串
import math
def clean_nan(obj):
if isinstance(obj, float) and (math.isnan(obj) or math.isinf(obj)):
return None
if isinstance(obj, dict):
return {k: clean_nan(v) for k, v in obj.items()}
if isinstance(obj, list):
return [clean_nan(item) for item in obj]
return obj
clean_data = clean_nan(data)
print(json.dumps(clean_data)) # {"value": null, "max": null}
Q3:JSON 中的键必须是字符串吗?
python
import json
# Python 字典的键可以是数字
data = {1: "one", 2: "two"}
# 转为 JSON 时,数字键会自动变成字符串
json_str = json.dumps(data)
print(json_str) # {"1": "one", "2": "two"}
# 解析回来后,键也是字符串
parsed = json.loads(json_str)
print(parsed) # {'1': 'one', '2': 'two'}
print(parsed[1]) # ❌ KeyError!
print(parsed["1"]) # ✅ "one"
Q4:如何 pretty print JSON(调试用)?
python
import json
data = {"users": [{"name": "张三", "age": 25}], "total": 1}
# 方法1:json.dumps + indent
print(json.dumps(data, ensure_ascii=False, indent=2))
# 方法2:pprint 模块
from pprint import pprint
pprint(data)
# 方法3:命令行快速格式化
# cat data.json | python -m json.tool
# 或
# python -m json.tool data.json
十五、速查表
python
导入模块: import json
字符串 → 对象: data = json.loads(json_string)
对象 → 字符串: json_str = json.dumps(data, ensure_ascii=False, indent=2)
文件 → 对象: data = json.load(file_object)
对象 → 文件: json.dump(data, file_object, ensure_ascii=False, indent=2)
中文不转义: ensure_ascii=False
美化输出: indent=2(或4)
键排序: sort_keys=True
压缩输出: separators=(",", ":")
处理特殊对象: default=自定义函数
类型对照:
JSON object ↔ Python dict
JSON array ↔ Python list
JSON string ↔ Python str
JSON number ↔ Python int/float
JSON true ↔ Python True
JSON false ↔ Python False
JSON null ↔ Python None