Python进阶教程:6_JSON 数据解析 —— 新手完全指南

一、什么是 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) 423.14 intfloat
布尔(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
相关推荐
Python私教1 小时前
API 输出模型怎么设计:从 model_dump() 到显式展示层
python·fastapi
Python私教2 小时前
本地 AI 工具服务该绑定 127.0.0.1 还是 0.0.0.0?
python·fastapi
卷无止境2 小时前
FastAPI 的Admin面板生态
后端·python
qq_448011162 小时前
C语言中的动态内存分配
c语言·开发语言·php
ctlover2 小时前
Streamlit 框架
python
ι:3 小时前
MATLAB 与 Python 搭建无人机地面站:优势、劣势与选型逻辑
python·matlab·无人机
船厂电气自动化ai大模型3 小时前
AI大模型与数学 第32课 函数凹凸性与二阶导数:拐点求解、凹凸区间计算(10道二阶导数计算题)
数据结构·人工智能·python·深度学习·算法
汉字萌萌哒3 小时前
2024CSP-J入门级C++真题详解
开发语言·c++
arbboter3 小时前
【网络工具】NetProxy网络代理用户手册
开发语言·网络·c#·网络代理·网络异常·代理工具