💡 开发中经常要存点临时配置、爬虫结果或者 API 响应缓存?用数据库太重,用普通文本手动解析又麻烦?今天的主角——Python 标准库 json,再加点第三方小工具,轻松搞定轻量级 JSON 文本存储与交互~

Python 中的 JSON 处理教程

JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,人好读机器也好解析,现在几乎是全场景通用的“通用语言”。本教程从基础到进阶,带你快速掌握 Python 中的 JSON 处理。


📌 JSON 基础

JSON 数据结构

JSON 只支持 6 种核心数据结构,非常简洁:

  • 对象 🗂️:花括号 {} 包裹的键值对,键必须是双引号字符串
  • 数组 📊:方括号 [] 包裹的有序值集合,可混合类型(但建议规范)
  • :可以是字符串(双引号)、数字、布尔值(true/false)、null、对象或数组

🔄 JSON 与 Python 类型对应关系

Python 的 json 模块会自动做双向映射,记清楚这张表就行:

JSON 类型Python 类型
objectdict
arraylist
stringstr
numberint/float
trueTrue
falseFalse
nullNone

📖 读取 JSON 数据

从字符串加载(json.loads

loads = load string,适合处理 API 返回的字符串、多行配置文本等。

import json

# 多行 JSON 字符串可以用三引号,合法 JSON 不强制缩进,但 Python 写好看点
json_str = '''
{
    "name": "John",
    "age": 30,
    "city": "New York",
    "hobbies": ["reading", "traveling"],
    "married": false
}
'''

# 转换为 Python 对象
data = json.loads(json_str)

print(type(data))          # <class 'dict'>
print(data["name"])        # John
print(data["hobbies"][0])  # reading

从文件加载(json.load

load = load file object,记得with 语句自动关闭文件。

import json

# 假设同目录下有 data.json 文件
try:
    with open('data.json', 'r', encoding='utf-8') as f:
        data = json.load(f)  # 直接传文件对象
except FileNotFoundError:
    print("文件不存在,创建默认数据...")
    data = {"default": True}

print(data)

✍️ 写入 JSON 数据

转为 JSON 字符串(json.dumps

dumps = dump string,三个必用的实用参数要记牢:

  1. ensure_ascii=False:保留中文等非 ASCII 字符(不加会变成 \uXXXX 乱码)
  2. indent=24:设置缩进层级,方便人阅读
  3. sort_keys=True:按字典键排序,输出结果更稳定(便于比较)
import json

data = {
    "name": "张三",
    "age": 28,
    "city": "北京",
    "hobbies": ["摄影", "编程"],
    "married": True
}

# 转换为带缩进、有序、保留中文的 JSON 字符串
json_str = json.dumps(data, ensure_ascii=False, indent=4, sort_keys=True)
print(json_str)

写入 JSON 文件(json.dump

dump = dump file object,参数和 dumps 一致。

import json

data = {
    "name": "李四",
    "age": 35,
    "city": "上海",
    "hobbies": ["游泳", "音乐"],
    "married": False
}

with open('output.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=4, sort_keys=True)

🚀 高级用法

处理自定义类 / 日期时间对象

标准的 json 模块无法直接序列化自定义类(如 User)或 datetime,可以用两种方法:

方法 1:自定义 default 函数(简单灵活)

dumps/dump 时传入 default,告诉模块怎么处理不认识的对象:

import json
from datetime import datetime

class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.created_at = datetime.now()

def custom_encoder(obj):
    # 先处理特殊类型
    if isinstance(obj, datetime):
        # 转 ISO 格式字符串,跨平台且可读
        return obj.isoformat()
    if isinstance(obj, User):
        # 显式指定要序列化的字段(比直接用 __dict__ 安全,不会带临时属性)
        return {
            "name": obj.name,
            "age": obj.age,
            "created_at": obj.created_at
        }
    # 其他类型交给默认处理(会抛出 TypeError)
    raise TypeError(f"Object of type {obj.__class__.__name__} not serializable")

user = User("王五", 40)
json_str = json.dumps(user, default=custom_encoder, ensure_ascii=False, indent=2)
print(json_str)

方法 2:继承 json.JSONEncoder(适合封装)

如果多个地方要用到同样的序列化逻辑,可以封装成子类,传入 cls 参数:

class CustomJSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        if isinstance(obj, User):
            return {
                "name": obj.name,
                "age": obj.age,
                "created_at": obj.created_at
            }
        return super().default(obj)

# 调用时传 cls
json_str = json.dumps(user, cls=CustomJSONEncoder, ensure_ascii=False, indent=2)

解析时自动还原特殊类型(object_hook

读取 JSON 时,每解析到一个 JSON 对象(即 Python 的 dict),都会调用 object_hook 函数,我们可以在这里把 ISO 格式的时间还原成 datetime,或者把普通 dict 还原成 User

import json
from datetime import datetime

def custom_decoder(dct):
    # 每解析到一个 dict 就检查
    if "created_at" in dct:
        try:
            dct["created_at"] = datetime.fromisoformat(dct["created_at"])
        except ValueError:
            pass  # 如果格式不对就保留字符串
    # 如果有 name 和 age,还可以还原成 User(可选)
    if "name" in dct and "age" in dct and "created_at" in dct:
        return User(dct["name"], dct["age"])
    return dct

json_str = '''
{
    "name": "赵六",
    "age": 45,
    "created_at": "2023-01-15T10:30:00"
}
'''

data = json.loads(json_str, object_hook=custom_decoder)
print(type(data))  # <class '__main__.User'>(如果上面做了还原)
print(type(data["created_at"]) if isinstance(data, dict) else type(data.created_at))  # <class 'datetime.datetime'>

流式处理大型 JSON 文件(用 ijson

如果 JSON 文件超过内存限制(比如几个 G 的日志或爬虫数据),可以用第三方库 ijson 逐块读取,不用一次性加载整个文件:

# 先安装 ijson
pip install ijson
import ijson

# 假设 large_users.json 是一个包含很多用户的数组:[{"name":...}, {"name":...}, ...]
with open('large_users.json', 'rb') as f:
    # 只流式读取并处理所有用户名,不用加载整个数组
    for name in ijson.items(f, 'item.name'):
        print(name)
        # 这里可以做插入数据库、统计等操作

✅ 最佳实践

  1. 编码问题必注意:读写文件加 encoding='utf-8',序列化加 ensure_ascii=False
  2. 文件操作必用 with:自动管理文件句柄,防止泄漏
  3. 错误处理必加:至少捕获 FileNotFoundError(文件不存在)、json.JSONDecodeError(解析错误,Python 3.5+)、TypeError(序列化错误)
  4. 大型数据必用流式ijsonpandas.read_json(chunksize=...) 都是好选择
  5. 安全性必考虑:不要解析来自不可信源的 JSON,防止恶意代码注入(虽然 Python 的 json 模块比 eval 安全,但还是要谨慎)

🎉 总结

Python 的标准库 json 已经能覆盖 90% 的日常 JSON 处理需求,搭配 ijson 等小工具,轻量级文本存储、API 交互、临时数据缓存都能轻松搞定~下次碰到这类需求,别再急着开数据库啦!