@toc
环境:Python 3.x | 适用人群:Python 初学者、需要处理 API 数据或配置文件的开发者
你是不是也遇到过这些问题?从 API 接口拿到一串 JSON 数据,却不知道如何高效地解析和提取信息;想把 Python 对象保存到文件,却发现直接序列化会报错;或者写配置文件时,总在逗号、引号这些语法细节上栽跟头?JSON 作为现代数据交换的"普通话",是每个 Python 开发者必须掌握的核心技能。
本文将从 JSON 与 Python 的类型映射讲起,带你理解序列化与反序列化的底层逻辑,然后通过实战案例演示如何解析 API 数据、处理自定义对象,最后总结出 3 个避坑技巧。读完本文,你不仅能掌握 json 模块的所有核心方法,还能获得一套可直接复用的代码模板,轻松应对日常开发中的 JSON 处理需求。
一、JSON 与 Python:类型映射全解析
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,Python 的 json 模块提供了两者间的双向转换能力。理解它们的类型对应关系是避免后续错误的基础。
| JSON 类型 | Python 类型 | 示例与注意事项 |
|---|---|---|
object |
dict |
{"name": "张三"} → {"name": "张三"} |
array |
list 或 tuple |
[1, 2, 3] → [1, 2, 3] |
string |
str |
"hello" → "hello" |
number |
int 或 float |
42 → 42, 3.14 → 3.14 |
true/false |
True/False |
注意 Python 首字母大写 |
null |
None |
JSON 的 null 对应 Python 的 None |
💡 关键区别:JSON 的键必须是字符串(双引号包裹),而 Python 字典的键可以是任何不可变类型(字符串、数字、元组)。这是序列化时最常见的类型错误来源之一。
很多人以为 json.dumps() 会把 Python 的 tuple 也转成 JSON 数组,这没错。但一个隐藏的细节是:当 JSON 数组被 json.loads() 反序列化时,它总是 变成 Python 的 list,而不是 tuple。如果你需要元组的不可变性,需要在反序列化后手动转换。
二、Python 到 JSON:序列化实战与避坑
序列化(Serialization)是将 Python 对象转换为 JSON 字符串的过程,主要通过 json.dumps()(输出字符串)和 json.dump()(写入文件)实现。
2.1 基础序列化:从字典到 JSON 字符串
下面的代码演示如何将一个包含嵌套结构的 Python 字典转换为格式化的 JSON 字符串:
python
import json
# 准备要序列化的数据
data = {
"name": "username",
"age": 18,
"is_teacher": False,
"id": "202205002626",
"address": {
"city": "xi an",
"town": "bigtown"
}
}
# 基础转换
json_str = json.dumps(data)
print(f"基础转换结果:{json_str}")
print(f"类型:{type(json_str)}")
运行结果:
arduino
基础转换结果:{"name": "username", "age": 18, "is_teacher": false, "id": "202205002626", "address": {"city": "xi an", "town": "bigtown"}}
类型:<class 'str'>
💡 Python 内部机制 :
json.dumps()在底层会递归遍历 Python 对象,根据上述类型映射表进行转换。对于bool类型,Python 的True/False会被转换为 JSON 的true/false(小写)。这个过程在 CPython 中由 C 语言编写的扫描器完成,速度很快。
2.2 美化输出:让 JSON 更易读
实际开发中,我们经常需要查看或调试 JSON 数据,indent 和 ensure_ascii 参数能显著提升可读性:
python
# 美化输出:缩进、中文支持、键排序
json_pretty = json.dumps(
data,
indent=2, # 缩进2个空格
ensure_ascii=False, # 中文不转义为 \uXXXX
sort_keys=True # 按键名字母顺序排序
)
print("美化后的 JSON:")
print(json_pretty)
运行结果:
javascript
美化后的 JSON:
{
"address": {
"city": "xi an",
"town": "bigtown"
},
"age": 18,
"id": "202205002626",
"is_teacher": false,
"name": "username"
}
2.3 写入文件:持久化存储配置
将数据保存到文件是配置管理、数据缓存的常见场景:
python
# 将数据写入 JSON 文件
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print("数据已成功写入 data.json 文件")
# 验证文件内容
with open("data.json", "r", encoding="utf-8") as f:
content = f.read()
print("文件内容预览:")
print(content[:100] + "..." if len(content) > 100 else content)
⚠️ 注意 :
ensure_ascii=False参数允许中文字符直接保存,而不是转义为\uXXXX格式。但某些旧系统或严格遵循 RFC 8259 的解析器可能要求纯 ASCII,请根据实际环境选择。写入文件时指定encoding='utf-8'是跨平台的最佳实践。
2.4 我踩过的坑:语法错误全解析
很多初学者(包括曾经的我)在写 JSON 或 Python 字典时容易犯以下错误:
错误1:使用 = 而不是 :
python
# ❌ 错误写法
data = {"name" = "username"} # SyntaxError
# ✅ 正确写法
data = {"name": "username"}
错误2:键值对之间缺少逗号
python
# ❌ 错误写法
data = {
"name": "username"
"age": 18 # 这里缺少逗号!
}
# ✅ 正确写法
data = {
"name": "username",
"age": 18 # 每个键值对后必须有逗号(最后一个可选)
}
错误3:使用中文标点
python
# ❌ 错误写法(中文逗号)
data = {"name": "username", "age": 18} # JSONDecodeError
# ✅ 正确写法(英文逗号)
data = {"name": "username", "age": 18}
💡 排查技巧 :当遇到
JSONDecodeError时,Python 会告诉你出错的行和列。仔细检查该位置附近的标点符号,90% 的问题都是逗号、引号或冒号使用不当。一个快速验证 JSON 格式的在线工具是 JSONLint。
三、JSON 到 Python:反序列化实战
反序列化(Deserialization)是将 JSON 字符串转换回 Python 对象的过程,主要通过 json.loads()(从字符串)和 json.load()(从文件)实现。
3.1 从字符串解析:处理 API 响应
假设你从某个 API 接口收到了以下 JSON 字符串:
python
import json
# 模拟 API 返回的 JSON 字符串
json_str = """{
"name": "username",
"age": 18,
"id": "2022552203"
}"""
print(f"原始字符串:{json_str}")
print(f"字符串类型:{type(json_str)}")
# 反序列化
python_obj = json.loads(json_str)
print(f"\n反序列化结果:{python_obj}")
print(f"结果类型:{type(python_obj)}")
print(f"访问字段 - 姓名:{python_obj['name']}")
运行结果:
arduino
原始字符串:{
"name": "username",
"age": 18,
"id": "2022552203"
}
字符串类型:<class 'str'>
反序列化结果:{'name': 'username', 'age': 18, 'id': '2022552203'}
结果类型:<class 'dict'>
访问字段 - 姓名:username
3.2 从文件读取:加载配置文件
更常见的场景是从 JSON 配置文件中读取数据:
python
# 从之前创建的 data.json 文件读取
with open("data.json", "r", encoding="utf-8") as f:
loaded_data = json.load(f) # 注意是 load() 不是 loads()
print(f"从文件加载的数据:{loaded_data}")
print(f"数据类型:{type(loaded_data)}")
print(f"嵌套访问 - 城市:{loaded_data['address']['city']}")
3.3 常见反序列化错误及解决
错误示例1:JSON 语法错误
python
# ❌ 错误写法:键未加双引号
invalid_json = """{
name: "username", # 键应该用双引号
"age": 18
}"""
# ❌ 错误写法:使用中文逗号
invalid_json2 = """{
"name": "username",
"age": 18
}"""
# ✅ 正确写法
valid_json = """{
"name": "username",
"age": 18
}"""
错误示例2:类型不匹配与数字精度
python
# JSON 中的数字可能超出 Python int 范围
big_num_json = '{"id": 999999999999999999999999999999}'
try:
result = json.loads(big_num_json)
print(f"大数字处理:{result['id']}") # Python 会自动转为 int
except Exception as e:
print(f"错误:{e}")
# 浮点数精度问题
float_json = '{"price": 0.1 + 0.2}' # 这本身是无效JSON,仅作示意
# 实际中,0.1+0.2在JSON中就是0.3,但Python计算0.1+0.2 != 0.3
💡 安全提醒 :从不可信来源(如用户输入、第三方 API)加载 JSON 时,考虑使用
json.loads()的object_hook参数进行验证和过滤,或使用demjson等更严格的解析器。避免直接eval()JSON 字符串,以防 JSON 注入攻击。
四、处理自定义对象:进阶序列化技巧
默认情况下,json 模块无法序列化自定义类的实例。但通过两种方式可以解决这个问题。
4.1 方法一:自定义编码器(继承 JSONEncoder)
这种方法适合需要多次序列化同一类对象的场景,代码更结构化。
python
import json
class Person:
"""人员信息类"""
def __init__(self, name, age):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name={self.name}, age={self.age})"
class PersonEncoder(json.JSONEncoder):
"""自定义 Person 类的 JSON 编码器"""
def default(self, obj):
# 如果是 Person 实例,转换为字典
if isinstance(obj, Person):
return {
"_type": "Person", # 添加类型标记,便于反序列化识别
"name": obj.name,
"age": obj.age
}
# 其他类型交给父类处理(会抛出 TypeError)
return super().default(obj)
# 创建 Person 实例
person = Person("张三", 25)
# 使用自定义编码器序列化
json_str1 = json.dumps(person, cls=PersonEncoder, ensure_ascii=False)
print(f"方法一结果:{json_str1}")
4.2 方法二:使用 default 回调函数
这种方法更灵活,适合临时处理或多种类型的序列化。
python
def person_to_dict(obj):
"""将 Person 对象转换为字典的回调函数"""
if isinstance(obj, Person):
return {
"_type": "Person",
"name": obj.name,
"age": obj.age
}
# 如果不是 Person 类型,抛出 TypeError
raise TypeError(f"对象类型 {type(obj)} 不可序列化")
# 使用 default 参数序列化
json_str2 = json.dumps(person, default=person_to_dict, ensure_ascii=False)
print(f"方法二结果:{json_str2}")
两种方法的输出相同:
json
{"_type": "Person", "name": "张三", "age": 25}
4.3 反序列化自定义对象
序列化只是第一步,我们还需要能将 JSON 恢复为原来的对象:
python
def dict_to_person(dct):
"""将字典转换回 Person 对象的回调函数"""
if dct.get("_type") == "Person":
# 根据字典中的信息重建 Person 对象
return Person(dct["name"], dct["age"])
# 如果不是 Person 类型,返回原字典
return dct
# 反序列化:使用 object_hook 参数
person_json = '{"_type": "Person", "name": "李四", "age": 30}'
restored_person = json.loads(person_json, object_hook=dict_to_person)
print(f"反序列化结果:{restored_person}")
print(f"类型:{type(restored_person)}")
print(f"访问属性 - 姓名:{restored_person.name}")
运行结果:
arduino
反序列化结果:Person(name=李四, age=30)
类型:<class '__main__.Person'>
访问属性 - 姓名:李四
💡 底层原理 :
object_hook参数会在解析每个字典 时被调用。如果返回非字典值,则该值会替换原来的字典。这使得我们可以在解析过程中"拦截"特定格式的字典,将其转换为自定义对象。cls参数同理,用于自定义解码器。
下面用 Mermaid 图展示自定义对象序列化与反序列化的完整流程:
我第一次实现自定义序列化时踩过一个坑:在 default 方法里没有处理好继承关系。当我尝试序列化一个 Person 的子类 Student 时,isinstance(obj, Person) 判断为 True,序列化成功了,但反序列化时 object_hook 只认 Person,丢失了子类的特有属性。解决方案是在字典里也保存类的完整名称(obj.__class__.__name__),并在 object_hook 里根据类名动态创建对象。
五、实战项目:解析 API 返回的订单数据
现在我们来完成一个完整的实战项目:解析一个模拟的电商 API 返回的订单数据。
5.1 准备测试数据
首先创建 pra_json.json 文件,内容如下:
json
{
"status": 1,
"message": "success",
"result": {
"userInfo": {
"uid": 5689,
"username": "study_json",
"createTime": "2025-08-12"
},
"order": [
{
"orderNo": "OD20250905001",
"payMoney": 258.6,
"product": [
{"pId": 221, "pName": "Python实战书籍"},
{"pId": 222, "pName": "编程配套课程"}
]
},
{
"orderNo": "OD20250905002",
"payMoney": 99,
"product": [
{"pId": 315, "pName": "JSON入门手册"}
]
}
]
}
}
5.2 完整解析代码
下面的代码演示了如何稳健地解析这个复杂的嵌套 JSON 结构:
python
import json
def parse_order_data(file_path):
"""
解析订单数据文件
Args:
file_path: JSON 文件路径
Returns:
dict: 解析后的数据,包含用户信息和订单列表
"""
try:
# 1. 读取并解析 JSON 文件
with open(file_path, "r", encoding="utf-8") as f:
data = json.load(f) # 关键:使用 load() 不是 dumps()
# 2. 检查接口状态
if data.get("status") != 1:
print(f"API 请求失败: {data.get('message', '未知错误')}")
return None
print("✅ 数据获取成功")
# 3. 提取用户信息
user_info = data["result"]["userInfo"]
print(f"\n👤 用户信息:")
print(f" 用户ID: {user_info['uid']}")
print(f" 用户名: {user_info['username']}")
print(f" 注册时间: {user_info['createTime']}")
# 4. 提取并处理订单信息
orders = data["result"]["order"]
total_amount = 0!")
运行结果: