基础内容可以参考:【JSON小白篇】数据交换的通用语言—JSON
Python dict 和 JSON 字符串之间的双向转换,dumps 把字典"倒出来"变成字符串,loads 把字符串"装回去"变成字典。
Python 的 json 模块是标准库的一部分,不需要 pip install,直接 import json 就能用。
一、核心就四个函数
先说名字。dump 的英文本意是"倾倒、卸货",像翻斗车把一车沙子哗地倒出来。在编程里取的就是这个意象:把内存里的数据"倒出来"变成外部格式。反过来 load 就是"装货",把外部数据"装回"程序。

这个比喻在整个计算机领域都通用:mysqldump 是把数据库倒出来,core dump 是把内存倒出来,tcpdump 是把网络包倒出来。
json 模块的 API 不少,但日常开发只需要四个:
| 函数 | 方向 | 输入 → 输出 | 场景 |
|---|---|---|---|
json.dumps() | Python → 字符串 | dict → str | 发给前端、存进 Redis |
json.loads() | 字符串 → Python | str → dict | 解析请求体、读取存储 |
json.dump() | Python → 文件 | dict → file | 写配置文件 |
json.load() | 文件 → Python | file → dict | 读配置文件 |
怎么记?带 s 的处理字符串(string),不带 s 的处理文件(file)。

四个函数分两对:dumps/loads 处理字符串,dump/load 处理文件。上面是序列化(Python → JSON),下面是反序列化(JSON → Python)。
二、dumps 和 loads:最常用的一对
2.1 把字典变成 JSON 字符串
import json
user = {"name": "zhangsan", "age": 28, "skills": ["Python", "Flask"]}
text = json.dumps(user)
print(f"dict to json = {text}")
print(f"type = {type(text)}")
dict to json = {"name": "zhangsan", "age": 28, "skills": ["Python", "Flask"]}
type = <class 'str'>
json.dumps 的返回值是一个普通的 Python 字符串。字典变成了一段符合 JSON 格式的文本,这个过程叫序列化(serialization)。
2.2 把 JSON 字符串变回字典
data = json.loads(text)
print(f"json to dict = {data}")
print(f"type = {type(data)}")
json to dict = {'name': 'zhangsan', 'age': 28, 'skills': ['Python', 'Flask']}
type = <class 'dict'>
json.loads 做的是反方向的事。把 JSON 字符串解析回 Python 对象。这叫反序列化(deserialization)。
一来一回,数据没有丢失。这是 json 模块最基本的用法:程序里用字典,传出去用字符串,收回来再变字典。
2.3 不只是字典
dumps 不只能处理字典,列表也行:
json.dumps([1, 2, 3]) # '[1, 2, 3]'
json.dumps("hello") # '"hello"'
json.dumps(42) # '42'
json.dumps(True) # 'true' ← 注意变成了小写
json.dumps(None) # 'null' ← 注意变成了 null
只要是 JSON 支持的类型,都能转。不过实际开发中 99% 的情况都是在转字典或列表。
三、dump 和 load:读写文件
和 dumps/loads 逻辑完全一样,只不过目标从字符串换成了文件:
3.1 写入文件
import json
config = {
"debug": True,
"port": 5000,
"allowed_hosts": ["localhost", "127.0.0.1"]
}
with open("config.json", "w") as f:
json.dump(config, f, indent=2)
生成的 config.json:
{
"debug": true,
"port": 5000,
"allowed_hosts": [
"localhost",
"127.0.0.1"
]
}
注意两个自动转换:Python 的 True 变成了 JSON 的 true,列表的格式也自动处理了。你不需要关心这些差异,json 模块全部搞定。
3.2 读取文件
with open("config.json") as f:
loaded = json.load(f)
print(f"port = {loaded['port']}") # 5000
print(f"debug = {loaded['debug']}, type = {type(loaded['debug'])}") # <class 'bool'> ← 自动转回 Python 的 True
port = 5000 debug = True, type = <class 'bool'>
读回来之后类型也是对的,true 变回 True,数字还是数字,列表还是列表。
3.3 dump 和 dumps 的关系
其实 dump 内部就是先调 dumps 拿到字符串,再写入文件。所以这两种写法效果一样:
# 方式一:直接用 dump
with open("config.json", "w") as f:
json.dump(config, f, indent=2)
# 方式二:先 dumps 再手动写
with open("config.json", "w") as f:
f.write(json.dumps(config, indent=2))
既然一样,为什么要有 dump?因为处理大文件时 dump 可以边序列化边写入,不需要先在内存里生成完整的字符串。日常用哪个都行,但写文件用 dump 是更地道的写法。
四、三个你一定会用到的参数
dumps和dump都支持这些参数。
4.1 indent:让输出好看
import json
user = {"name": "zhangsan", "age": 28, "skills": ["Python", "Flask"]}
# 默认:紧凑一行
print("===默认输出格式===")
print(json.dumps(user))
# 加了 indent=2:有缩进有换行
print("====美化后=====")
print(json.dumps(user, indent=2))
indent 的值是缩进的空格数。indent=2 就是每层缩进 2 个空格,indent=4 就是 4 个。
输出:
===默认输出格式=== {"name": "zhangsan", "age": 28, "skills": ["Python", "Flask"]} ====美化后===== { "name": "zhangsan", "age": 28, "skills": [ "Python", "Flask" ] }
什么时候加?给人看的时候加,给机器看的时候别加。 写配置文件、调试时 print 出来看,加 indent 让人舒服。发给前端、存进 Redis,不加,多余的空格和换行只会浪费带宽和内存。
4.2 ensure_ascii:让中文正常显示
data = {"城市": "北京", "人口": "2100万"}
print("====默认输出====")
print(json.dumps(data))
print("=====设置中文直接输出=====")
print(json.dumps(data, ensure_ascii=False))
默认情况下,json.dumps 会把**所有非 ASCII 字符转成 \uXXXX 转义序列。这在技术上完全没问题,任何 JSON 解析器都能正确还原,但人读起来很痛苦**。加上 ensure_ascii=False 就能直接输出中文。
输出:
====默认输出==== {"\u57ce\u5e02": "\u5317\u4eac", "\u4eba\u53e3": "2100\u4e07"} =====设置中文直接输出===== {"城市": "北京", "人口": "2100万"}
写文件的时候记得同时指定编码:
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
写入的json文件如下:
{
"城市": "北京",
"人口": "2100万"
}
4.3 separators:控制紧凑程度
payload = {"user_id": 123, "name": "zhangsan"}
# 默认:逗号后有空格,冒号后有空格
print("====默认展示====")
print(json.dumps(payload))
# 紧凑格式:去掉所有多余空格
print("====紧凑格式====")
print(json.dumps(payload, separators=(",", ":")))
separators 接受一个元组 (逗号分隔符, 冒号分隔符)。默认是 (", ", ": ")(逗号和冒号后面各有一个空格),改成 (",", ":") 就把空格全去掉了。
输出:
====默认展示==== {"user_id": 123, "name": "zhangsan"} ====紧凑格式==== {"user_id":123,"name":"zhangsan"}
这个参数日常用得不多,但在需要精确控制输出格式的场景很关键。比如 JWT 的 Payload 编码就用紧凑格式,多一个空格,Base64 编码结果就不一样,签名就对不上。
4.4 参数可以组合
这三个参数互不冲突,可以一起用:
data = {"姓名": "张三", "年龄": 28}
print(json.dumps(data, indent=2, ensure_ascii=False))
{
"姓名": "张三",
"年龄": 28
}
不过 indent 和 separators=(",", ":") 一般不同时用,加了缩进就是为了好看,再去空格就矛盾了。
五、类型映射:Python 和 JSON 不是一一对应的
这是用 json 模块最容易踩坑的地方。序列化和反序列化时,Python 类型和 JSON 类型之间有一张固定的映射表:
| Python 类型 | → JSON 类型 | → 读回来的 Python 类型 |
|---|---|---|
dict | object {} | dict |
list | array [] | list |
tuple | array [] | list ← 回不去了 |
str | string | str |
int | number | int |
float | number | float |
True / False | true / false | True / False |
None | null | None |
大部分能完美往返,但注意有两个坑。
5.1 坑一:tuple 转出去就回不来了
JSON 里没有 tuple 这个概念,只有 array。所以 tuple 序列化后和 list 长得一样,读回来自然也是 list。如果你的代码依赖 tuple 的不可变性,来回转换后会出问题。
-
tuple类型:
data = {"coords": (10, 20)} text = json.dumps(data) print(text){"coords": [10, 20]} -
从JSON读回:
loaded = json.loads(text) print(type(loaded["coords"]))<class 'list'>
5.2 坑二:整数键会被悄悄转成字符串
JSON 规范要求键必须是字符串,所以 dumps 会自动把整数键转成字符串。但 loads 读回来不会帮你转回整数,它只按 JSON 规范来,键就是字符串。
import json
data = {1: "one", 2: "two"}
text = json.dumps(data)
print("=====dict to json=====")
print(text)
====dict to json==== {"coords": [10, 20]}
如果你的字典用整数做键,来回转换后会对不上。
loaded = json.loads(text)
print("====json to dict=====")
print(loaded[1])
====json to dict===== Traceback (most recent call last): File "/02_JSON用法Python篇/code/scripts/integer_demo.py", line 11, in <module> print(loaded[1]) KeyError: 1
5.3 不在表里的类型:直接报错
datetime、set、bytes、自定义对象都不在映射表里,dumps 遇到它们就直接抛 TypeError。
from datetime import datetime
json.dumps({"time": datetime.now()})
# TypeError: Object of type datetime is not JSON serializable
(1) 手动转格式
最简单的办法是手动转:
from datetime import datetime, timezone
data = {
"event": "login",
"time": datetime.now(timezone.utc).isoformat(), # datetime → 字符串
"tags": list({1, 2, 3}), # set → list
}
print(json.dumps(data))
{"event": "login", "time": "2026-03-26T03:50:03.946895+00:00", "tags": [1, 2, 3]}
datetime 转成 ISO 格式字符串是业界惯例,前端拿到后可以直接 new Date() 解析。
(2) 使用default指定处理方法
如果项目里到处都有 datetime,每次手动转太烦,可以用 default 参数写一个统一的转换函数:
def json_default(obj):
"""json.dumps 遇到不认识的类型时会调用这个函数"""
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, set):
return list(obj)
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
default 参数接受一个函数:每当 dumps 遇到映射表里没有的类型,就把那个对象丢给这个函数,函数返回什么 JSON 里就写什么。写一次,到处复用。
data = {"time": datetime.now(timezone.utc), "tags": {1, 2, 3}}
print(json.dumps(data, default=json_default))
输出:
{"time": "2026-03-26T03:53:23.265227+00:00", "tags": [1, 2, 3]}
六、错误处理:两种常见异常
用 json 模块基本只会遇到以下两种错误。
6.1 序列化失败:TypeError
把不支持的类型丢给 dumps:
import json
from datetime import datetime
try:
json.dumps({"time": datetime.now()})
except TypeError as e:
print(e) # Object of type datetime is not JSON serializable
解决办法上面讲过了,手动转类型或用 default 参数。
6.2 反序列化失败:JSONDecodeError
把不合法的 JSON 字符串丢给 loads:
try:
json.loads("{'name': 'zhangsan'}") # 单引号,不是合法 JSON
except json.JSONDecodeError as e:
print(e) # Expecting property name enclosed in double quotes
try:
json.loads("") # 空字符串
except json.JSONDecodeError as e:
print(e) # Expecting value
JSONDecodeError 的报错信息通常很明确,会告诉你第几行第几个字符出了问题。常见原因:单引号代替了双引号、尾逗号、缺少引号、字符串没转义。
七、总结
| 你想做的事 | 用什么 |
|---|---|
| 字典 → JSON 字符串 | json.dumps(data) |
| JSON 字符串 → 字典 | json.loads(text) |
| 字典 → JSON 文件 | json.dump(data, f) |
| JSON 文件 → 字典 | json.load(f) |
| 输出好看 | indent=2 |
| 中文不转义 | ensure_ascii=False |
| 紧凑无空格 | separators=(",", ":") |
| datetime / set 报错 | 手动转,或用 default 参数 |
| 解析报错 | 捕获 json.JSONDecodeError |
json 模块的本质就是 Python 对象和 JSON 文本之间的翻译器。四个核心函数、三个常用参数、一张类型映射表、两种异常。记住这些,单独用 json 模块就没有盲区了。
1161



被折叠的 条评论
为什么被折叠?



