4、JSON数据处理:JSON语法规则、Python中的json模块、序列化与反序列化、复杂嵌套结构处理

JSON,全称 JavaScript Object Notation。说白了,它就是一套数据交换的通用语言。你想想看,桌面应用要和后端API通信,总得有个双方都认的格式吧?XML太重了,纯文本又太简陋。JSON正好卡在中间——轻量、易读、解析快。

我个人习惯把JSON叫做「结构化文本」。它看起来像Python的字典和列表,但又不完全是。嗯,这里要注意:JSON的语法比Python严格得多。我见过太多新手因为多写了一个逗号,调试了半天。

4.1 JSON语法规则——别踩这些坑

JSON的语法其实很简单,就几条规则。但越是简单的东西,越容易在细节上翻车。我曾经在一个项目里,因为JSON的key忘记加双引号,导致整个数据解析失败。那次排查花了我整整两个小时。

来看核心规则:

  • 键必须用双引号——Python里你可以写 {'name': '张三'},但JSON必须写成 {"name": "张三"}。单引号?不行。
  • 字符串必须用双引号——和上面同理。值如果是字符串,也得双引号包着。
  • 不能有尾随逗号——Python里 [1, 2, 3,] 是合法的,但JSON里最后一个元素后面不能有逗号。
  • 值类型有限制——只支持:字符串、数字、布尔值、null、数组、对象。Python的元组、集合、datetime?统统不支持。

一个合法的JSON示例:

{
  "name": "数据采集工具",
  "version": 2.1,
  "is_active": true,
  "features": ["实时监控", "日志导出"],
  "config": {
    "timeout": 30,
    "retry": null
  }
}

为什么JSON要这么严格?说白了,就是为了跨语言解析时不出歧义。你想想看,Python的字典和JavaScript的对象长得像,但底层实现完全不同。JSON就是那个「最大公约数」。

4.2 Python中的json模块——你的瑞士军刀

Python标准库自带的 json 模块,我几乎每天都在用。它提供了四个核心函数,两两对应:

函数 作用 输入 输出
json.dumps() 序列化(Python → JSON字符串) Python对象 字符串
json.loads() 反序列化(JSON字符串 → Python) 字符串 Python对象
json.dump() 序列化并写入文件 Python对象 + 文件对象 无(直接写入文件)
json.load() 从文件读取并反序列化 文件对象 Python对象

我建议你记住这个规律:带s的是操作字符串,不带s的是操作文件。dumps的s代表string,loads同理。这样就不会搞混了。

我的小技巧:调试时先用 json.dumps(data, indent=2, ensure_ascii=False)。indent让输出格式化,ensure_ascii=False保证中文不被转义成\uXXXX。这两个参数我几乎每次都加。

4.3 序列化与反序列化——数据变形记

序列化,就是把Python对象变成JSON字符串。反序列化,就是反过来。听起来简单,但实际项目中坑不少。

先看一个典型的序列化场景:

import json

# 一个桌面应用的配置数据
app_config = {
    "app_name": "网络监控器",
    "version": "1.3.0",
    "window_size": [1280, 720],
    "is_maximized": False,
    "plugins": None
}

# 序列化成JSON字符串
json_str = json.dumps(app_config, indent=2, ensure_ascii=False)
print(json_str)

输出结果:

{
  "app_name": "网络监控器",
  "version": "1.3.0",
  "window_size": [1280, 720],
  "is_maximized": false,
  "plugins": null
}

注意看,Python的 False 变成了 falseNone 变成了 null。这就是序列化的「翻译」过程。

反序列化则相反:

# 从API返回的JSON字符串
api_response = '{"status": "ok", "data": {"count": 42, "items": ["a", "b"]}}'

# 反序列化成Python字典
parsed = json.loads(api_response)
print(parsed["data"]["count"])  # 输出 42

我曾经踩过的坑:反序列化时,JSON的 truefalse 会变成Python的 TrueFalse(注意大小写)。如果你用 if data["flag"] == "true" 去判断,永远不成立。正确写法是 if data["flag"] 或者 if data["flag"] is True

4.4 复杂嵌套结构处理——实战中的硬骨头

真实项目中的JSON,很少像上面那么规整。我处理过最复杂的一个API返回,嵌套了7层,里面还有数组套对象、对象套数组。嗯,这里分享几个实战技巧。

4.4.1 安全访问嵌套数据

直接 data["key1"]["key2"] 很容易报KeyError。我习惯用 .get() 方法:

# 不安全的写法
# name = data["user"]["profile"]["name"]  # 可能崩溃

# 安全的写法
name = data.get("user", {}).get("profile", {}).get("name", "未知用户")

为什么用空字典 {} 作为默认值?因为这样链式调用不会断。你想想看,如果 data.get("user") 返回了None,再调用 .get() 就会报AttributeError。用空字典就安全了。

4.4.2 处理数组中的对象

API经常返回这种结构:

{
  "users": [
    {"id": 1, "name": "张三", "roles": ["admin"]},
    {"id": 2, "name": "李四", "roles": ["editor", "viewer"]},
    {"id": 3, "name": "王五", "roles": []}
  ]
}

要提取所有有admin角色的用户,可以这样:

admins = [user for user in data["users"] if "admin" in user.get("roles", [])]

列表推导式配合 .get(),一行搞定。我经常用这种写法处理批量数据。

4.4.3 自定义序列化——处理特殊类型

Python的datetime、Decimal等类型,json模块默认不认识。这时候需要自定义序列化器:

import json
from datetime import datetime

class CustomEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.strftime("%Y-%m-%d %H:%M:%S")
        # 可以继续添加其他类型处理
        return super().default(obj)

# 使用自定义编码器
data = {
    "event": "系统启动",
    "time": datetime.now()
}
json_str = json.dumps(data, cls=CustomEncoder, ensure_ascii=False)
print(json_str)
# 输出: {"event": "系统启动", "time": "2025-01-15 14:30:00"}

我的建议:如果项目中频繁处理特殊类型,可以封装一个通用的 to_json() 函数,把常用的类型转换都写进去。这样团队里所有人都用同一个标准,避免格式不统一。

4.5 实战:从API获取并处理嵌套JSON

最后,我们串起来看一个完整的例子。假设我们要从一个天气API获取数据:

import json
import urllib.request

# 模拟API返回的JSON数据
api_response = '''
{
  "code": 200,
  "message": "success",
  "data": {
    "city": "北京",
    "update_time": "2025-01-15 14:00:00",
    "weather": {
      "current": {
        "temp": -2,
        "humidity": 45,
        "wind": {"speed": 3.5, "direction": "北风"}
      },
      "forecast": [
        {"date": "01-15", "high": 2, "low": -8, "type": "晴"},
        {"date": "01-16", "high": 0, "low": -10, "type": "多云"},
        {"date": "01-17", "high": -1, "low": -12, "type": "小雪"}
      ]
    }
  }
}
'''

# 反序列化
weather_data = json.loads(api_response)

# 安全提取数据
city = weather_data.get("data", {}).get("city", "未知城市")
current_temp = weather_data.get("data", {}).get("weather", {}).get("current", {}).get("temp", "N/A")

# 提取未来三天最低温度
forecast = weather_data.get("data", {}).get("weather", {}).get("forecast", [])
min_temps = [day.get("low") for day in forecast if day.get("low") is not None]

print(f"城市: {city}")
print(f"当前温度: {current_temp}°C")
print(f"未来三天最低温: {min_temps}")

这个例子涵盖了本节所有知识点:JSON语法、序列化/反序列化、安全访问嵌套结构、列表推导式处理数组。你在实际开发中,遇到的API返回只会比这个更复杂,不会更简单。掌握了这些技巧,至少能应付80%的场景。

记住一句话:处理JSON,安全第一,优雅第二。先保证不崩溃,再考虑代码写得漂不漂亮。我早期做项目时,就因为太追求「一行代码搞定」,结果线上出了好几次事故。从那以后,我宁愿多写几行 .get(),也不冒KeyError的风险。