JSON 数据处理

JSON 是当今世界最常用的数据交换格式——API 返回、配置文件、日志、数据库字段……几乎无处不在。Python 内置的 json 模块非常够用。这一篇讲清楚 json 模块的四个核心函数、编码/解码规则、自定义序列化、以及处理”日期时间”、”Decimal”、”自定义类”这些默认不支持的类型的技巧。

一、四个核心函数

1
2
3
4
5
6
7
8
9
10
11
import json

# ============ 序列化(Python → JSON)============

json.dumps(obj) # → str
json.dump(obj, f) # 写入文件对象

# ============ 反序列化(JSON → Python)============

json.loads(s) # str → Python 对象
json.load(f) # 从文件读取

记忆:带 s 的是 str 版本,不带 s 的操作文件。

二、类型映射

Python ↔ JSON 类型对应关系:

Python JSON
dict object
list, tuple array
str string
int, float number
True / False true / false
None null

JSON 不支持的常见类型

  • datetime / date
  • Decimal
  • set / frozenset
  • bytes
  • 自定义类

以上都会引发 TypeError,需要自己转换。

三、序列化的常用参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
data = {"name": "咖飞", "age": 3, "tags": ["python", "学习"]}

# 默认:紧凑单行
json.dumps(data)
# '{"name": "\\u5496\\u98de", "age": 3, "tags": ["python", "\\u5b66\\u4e60"]}'

# 中文正常显示
json.dumps(data, ensure_ascii=False)
# '{"name": "咖飞", "age": 3, "tags": ["python", "学习"]}'

# 缩进美化
json.dumps(data, ensure_ascii=False, indent=2)
# {
# "name": "咖飞",
# "age": 3,
# "tags": ["python", "学习"]
# }

# 排序 key(用于稳定输出)
json.dumps(data, ensure_ascii=False, sort_keys=True)

# 自定义分隔符(更紧凑)
json.dumps(data, separators=(",", ":"))

几乎所有场景都建议 ensure_ascii=False——除非你的下游只能处理 ASCII。

四、读写文件

1
2
3
4
5
6
7
# 写
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)

# 读
with open("data.json", encoding="utf-8") as f:
data = json.load(f)

五、常见错误

编码错

1
2
3
4
5
json.dumps({datetime.now(): "x"})
# TypeError: keys must be str, int, float, bool or None, not datetime

json.dumps({1, 2, 3})
# TypeError: Object of type set is not JSON serializable

解码错

1
2
3
4
5
6
7
8
json.loads("{'a': 1}")
# JSONDecodeError: JSON 只允许双引号

json.loads("undefined")
# JSONDecodeError

json.loads(open("data.json").read())
# 可能因为 UTF-8 BOM 报错,用 encoding='utf-8-sig'

六、处理”不支持”的类型

方法 1:自定义 default 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from datetime import datetime, date
from decimal import Decimal


def json_default(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, Decimal):
return str(obj)
if isinstance(obj, set):
return list(obj)
raise TypeError(f"不支持的类型:{type(obj)}")


data = {
"time": datetime.now(),
"amount": Decimal("3.14"),
"tags": {"python", "json"},
}

print(json.dumps(data, default=json_default, ensure_ascii=False))
# {"time": "2025-09-26T10:12:47", "amount": "3.14", "tags": ["python", "json"]}

方法 2:继承 JSONEncoder

1
2
3
4
5
6
7
8
9
10
class MyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat()
if isinstance(obj, Decimal):
return str(obj)
return super().default(obj)


print(json.dumps(data, cls=MyEncoder))

两种方法功能等价,看喜好。

七、反序列化自定义处理

object_hook 让你在解析每个字典时干预:

1
2
3
4
5
6
7
8
9
10
11
12
def parse_datetime(d):
for k, v in d.items():
if isinstance(v, str) and len(v) >= 10 and v[4] == "-":
try:
d[k] = datetime.fromisoformat(v)
except ValueError:
pass
return d


json.loads('{"time": "2025-09-26T10:12:47"}', object_hook=parse_datetime)
# {'time': datetime.datetime(2025, 9, 26, 10, 12, 47)}

八、dataclass 与 JSON

如果你的数据是 dataclass,序列化很简单:

1
2
3
4
5
6
7
8
9
10
11
12
from dataclasses import dataclass, asdict
import json


@dataclass
class User:
name: str
age: int


u = User("咖飞", 3)
json.dumps(asdict(u), ensure_ascii=False)

反序列化:

1
User(**json.loads(s))

注意:字段类型不一致(比如 JSON 里 age 是字符串),要手动转。用第三方 pydantic / msgspec 能优雅解决这个。

九、pydantic 简介

pydantic 是 Python 里最强大的”JSON ↔ 类”工具。安装:pip install pydantic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pydantic import BaseModel
from datetime import datetime


class User(BaseModel):
name: str
age: int
joined: datetime


# 自动解析 + 类型校验
u = User.model_validate_json('{"name": "咖飞", "age": 3, "joined": "2025-09-26T10:00:00"}')
print(u.joined) # datetime 对象

# 输出 JSON
print(u.model_dump_json(indent=2))

pydantic 处理 datetime、UUID、Decimal、枚举、可选字段、嵌套模型都很自然。API 项目(FastAPI)里几乎必用。

十、JSON Lines:一行一个 JSON

大数据场景常用 JSON Lines(.jsonl):每行是一个独立的 JSON,用换行分隔。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 写
with open("events.jsonl", "w", encoding="utf-8") as f:
for e in events:
f.write(json.dumps(e, ensure_ascii=False) + "\n")

# 读(惰性,适合超大文件)
def read_jsonl(path):
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line:
yield json.loads(line)


for event in read_jsonl("events.jsonl"):
process(event)

十一、性能提示

  • json 是 Python 自带的纯 Python + C 加速实现,够快;
  • 更快的替代:orjson(Rust 写,2-5 倍快)、ujson
  • 处理超大 JSON 用 流式解析ijson 库),避免一次读进内存。

orjson 用法:

1
2
3
4
5
import orjson

# 直接返回 bytes
b = orjson.dumps({"a": 1, "time": datetime.now()})
d = orjson.loads(b)

orjson 原生支持 datetime、UUID、dataclass、numpy。

十二、安全性

不要用 eval 解析 JSON

1
2
3
4
5
# ❌ 危险
data = eval(json_str) # 攻击者可以注入代码

# ✅ 用 json
data = json.loads(json_str)

json.loads 只解析纯数据,不会执行代码。

十三、schema 校验

生产 API 需要严格的 schema:

  • jsonschema:标准 JSON Schema 校验;
  • pydantic:类型注解驱动的模型;
  • msgspec:极快的替代。

例子(pydantic):

1
2
3
4
5
6
7
8
9
10
11
class Order(BaseModel):
id: int
amount: float
status: Literal["pending", "paid", "shipped"]

# 校验通过
Order.model_validate({"id": 1, "amount": 99.9, "status": "paid"})

# 校验失败
Order.model_validate({"id": "x", "amount": 99.9, "status": "unknown"})
# ValidationError: 详细报告哪个字段错了

十四、一个完整例子:配置文件加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# config.py
"""一个 JSON 配置文件加载器,支持默认值、环境变量覆盖、类型转换。"""

import json
import os
from pathlib import Path
from datetime import datetime
from dataclasses import dataclass, field, asdict


DEFAULT_CONFIG = {
"app_name": "咖飞助手",
"debug": False,
"port": 8080,
"features": {"chat": True, "search": False},
"started_at": datetime.now().isoformat(),
}


def load_config(path: Path) -> dict:
"""加载配置文件;不存在则用默认。"""
if path.exists():
config = json.loads(path.read_text(encoding="utf-8"))
else:
config = DEFAULT_CONFIG.copy()
save_config(path, config)

# 环境变量覆盖(大写 + APP_ 前缀)
for key, value in config.items():
env_key = f"APP_{key.upper()}"
if env_key in os.environ:
raw = os.environ[env_key]
if isinstance(value, bool):
config[key] = raw.lower() in ("1", "true", "yes")
elif isinstance(value, int):
config[key] = int(raw)
else:
config[key] = raw
return config


def save_config(path: Path, config: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
json.dumps(config, ensure_ascii=False, indent=2),
encoding="utf-8",
)


if __name__ == "__main__":
cfg = load_config(Path("config.json"))
print(cfg)

十五、常见陷阱

  1. ensure_ascii=False:中文变 \uXXXX
  2. JSON 只支持双引号:单引号 JSON 用 ast.literal_eval 或改数据。
  3. JSON 数字精度json.loads('{"x": 3.14e400}') 会转 inf;大整数没问题。
  4. NaN / Infinity:Python json.dumps(float('nan')) 输出 NaN(非标准 JSON!)。用 allow_nan=False 让它抛错。
  5. 序列化 datetime 报错:写 default 函数或 dataclass + asdict。
  6. 文件 BOM:用 encoding="utf-8-sig"
  7. JSON key 必须是字符串{1: 'x'} 序列化会把 int 转字符串(危险,反序列化后是字符串了)。

十六、小结与延伸阅读

  • 四大 API:dumps / loads / dump / load
  • 永远 ensure_ascii=False
  • 想美化用 indent=2
  • 不支持的类型用 default= 函数或 JSONEncoder 子类;
  • 反序列化自定义用 object_hook
  • 高性能用 orjson
  • 类型安全用 pydantic
  • 大数据流用 JSON Lines + 生成器。

延伸阅读:

下一篇 常用标准库:os、sys、pathlib 我们讲文件系统与运行环境的工具箱。