Python JSON 完全指南:从基础到实战,掌握数据交换核心技能

@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 listtuple [1, 2, 3][1, 2, 3]
string str "hello""hello"
number intfloat 4242, 3.143.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 数据,indentensure_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 图展示自定义对象序列化与反序列化的完整流程:

sequenceDiagram participant P as Person对象 participant D as 字典 participant J as JSON字符串 participant F as 文件/网络 Note over P,D: 序列化 P->>D: 1. 自定义编码器/default函数 D->>J: 2. json.dumps() J->>F: 3. 存储/传输 Note over F,P: 反序列化 F->>J: 4. 读取/接收 J->>D: 5. json.loads() D->>P: 6. object_hook转换

我第一次实现自定义序列化时踩过一个坑:在 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!")
运行结果:
相关推荐
Cosolar1 小时前
深度测评 | QoderWork:当 AI 不再只是"聊天搭子",而是真能帮你干活的桌面智能体
人工智能·后端·程序员
前端Hardy1 小时前
21.8 万周下载!这个 React 表格组件,10 行代码就能跑起来
前端·javascript·后端
用户8356290780511 小时前
使用 Python 在 Word 文档中添加和管理脚注
后端
字节跳动数据库2 小时前
一个请求稳定的一生
后端·程序员
RainCity2 小时前
Java Swing 自定义组件库分享(十一)
java·笔记·后端
掘金一周3 小时前
问卷调查:如果现在收到裁员通知,你手里的现金流能支撑多久? | 沸点周刊6.4
前端·人工智能·后端
JustHappy4 小时前
古法编程秘籍(四):函数究竟是什么?把函数最重要的能力一次讲清楚
前端·后端·面试
_Evan_Yao4 小时前
一文搞懂:Git分支管理与团队协作规范——从GitFlow到GitHub Flow,从rebase到merge,打造高效协作流
java·git·后端·github
得物技术4 小时前
用 LLM Agent 重构告警排查流程|得物技术
java·人工智能·后端