json 把 Python 中的基本数据转换为 JSON 文本,也能把 JSON 文本解析回来。它适合配置、接口消息和跨语言交换;转换过程保留约定的数据值,不保留 Python 对象身份或所有类型。
本文按 Python 3.11 的标准库接口组织示例。
四个接口:字符串与文件分别处理
跳转到“四个接口:字符串与文件分别处理”| 接口 | 输入 | 输出或作用 |
|---|---|---|
json.dumps(obj) | Python 对象 | 返回 str |
json.loads(text) | JSON str;也支持符合要求的 bytes、bytearray | 返回 Python 对象 |
json.dump(obj, file) | 对象与能写入字符串的文件对象 | 向文件写 JSON 文本 |
json.load(file) | 能读取 JSON 内容的文件对象 | 解析一个 JSON 文档 |
dumps 返回的不是字节流。写普通 JSON 文件时,明确使用文本模式和 UTF-8;传输字节时,再对字符串调用 .encode("utf-8")。ensure_ascii=False 控制中文是否保留为字符,它并不负责设置文件编码。Python json:基本接口
下面是可直接运行的往返示例。它先创建数据和文件,不依赖工作目录里预先存在 person.json。
import jsonfrom pathlib import Pathfrom tempfile import TemporaryDirectory
person = { "name": "小林", "age": 20, "active": True, "address": {"city": "北京", "street": "中山路"}, "nickname": None,}
text = json.dumps(person, ensure_ascii=False, indent=2, allow_nan=False)assert isinstance(text, str)assert "小林" in textassert json.loads(text) == personassert json.loads(text.encode("utf-8")) == person
with TemporaryDirectory() as directory: path = Path(directory) / "person.json" with path.open("w", encoding="utf-8") as file: json.dump(person, file, ensure_ascii=False, indent=2, allow_nan=False) file.write("\n") with path.open("r", encoding="utf-8") as file: restored = json.load(file) assert restored == person值能往返,不代表类型和关系原样往返
跳转到“值能往返,不代表类型和关系原样往返”| Python 输入 | JSON 表示 | 默认解析结果与边界 |
|---|---|---|
dict | object | 恢复为 dict;JSON 键是字符串 |
list、tuple | array | 都恢复为 list,元组类型丢失 |
str | string | 恢复为 str |
int、有限 float | number | 通常恢复为 int、float;接收方的数值范围和精度可能不同 |
True、False | true、false | 恢复为布尔值 |
None | null | 恢复为 None |
set、bytes、日期、自定义实例 | 无默认编码 | 先制定转换约定,或提供明确的 default 编码函数 |
json.loads(json.dumps({1: "one"})) 得到 {"1": "one"}。如果输入同时含整数键 1 和字符串键 "1",转换后还会发生名称冲突。接口数据最好从开始就使用字符串键。
两个字段引用同一个列表时,JSON 可以写出两份相同的数组,但解析结果不会保留原来的共享关系;循环引用默认会报错。它因此不能代替 copy 的对象图复制 或 pickle 的 Python 对象序列化。类型转换表
格式控制与常见解析误解
跳转到“格式控制与常见解析误解”indent=2 便于阅读;separators=(",", ":") 删除不必要的分隔空格;sort_keys=True 便于比较结果,但不等于实现某个密码学签名所要求的规范化格式。
JSON 字符串和键使用双引号,不支持 Python 的单引号字面量、注释或尾随逗号。JSON 文档的顶层可以是数组、对象,也可以是一个字符串、数字、布尔值或 null;要求顶层必须为对象是应用自己的约定。
Python 默认编码允许输出 NaN、Infinity,默认解析也接受这些扩展值;其他 JSON 实现未必接受。编码时可设 allow_nan=False。解析对象遇到重复键时默认保留最后一个值,若业务不允许覆盖,需要自己拒绝。兼容性说明
为配置读取加上明确约束
跳转到“为配置读取加上明确约束”下面的读取函数只接受字符串输入,并明确拒绝过长输入、重复键、非有限浮点数及非对象顶层。parse_constant 拒绝 NaN 等名字;parse_float 还处理 1e400 这类普通数字文本转换为无穷大的情况。这不是完整的业务 schema 验证,字段名、类型、范围仍应另外检查。
import jsonimport math
def load_config(text): if not isinstance(text, str): raise TypeError("configuration must be text") if len(text.encode("utf-8")) > 65_536: raise ValueError("configuration exceeds 64 KiB")
def unique_object(pairs): result = {} for key, value in pairs: if key in result: raise ValueError(f"duplicate key: {key}") result[key] = value return result
def finite_float(token): value = float(token) if not math.isfinite(value): raise ValueError("non-finite number") return value
def reject_constant(token): raise ValueError(f"unsupported numeric constant: {token}")
result = json.loads( text, object_pairs_hook=unique_object, parse_float=finite_float, parse_constant=reject_constant, ) if not isinstance(result, dict): raise ValueError("configuration root must be an object") return result
assert load_config('{"name":"传感器","gain":1.25}') == { "name": "传感器", "gain": 1.25}for invalid in ('{"x":1,"x":2}', '{"x":NaN}', '{"x":1e400}', '[1,2]'): try: load_config(invalid) except ValueError: pass else: raise AssertionError(f"unexpectedly accepted: {invalid}")数字需要十进制精确语义时,可以通过 parse_float=decimal.Decimal 接收浮点形式的数字文本;这同时改变了解析结果的类型,后续再次写出 JSON 时也要约定编码方式。strict=True 主要约束字符串中的控制字符,不能替代上述重复键或非有限数检查。解码器参数
解析语法失败时,JSONDecodeError 提供行号、列号和位置,可用于报错定位。限制输入大小只能控制一部分资源消耗;复杂度、嵌套深度与字段验证仍是读取流程的一部分。
一个文件包含一个文档
跳转到“一个文件包含一个文档”连续对同一文件调用两次 json.dump,通常会拼成两个相邻文档,不能再作为单个 JSON 文档交给 json.load。少量记录可写成一个列表;逐行记录则应明确采用 JSON Lines 约定,每行单独编码、单独解析,且每条记录内部不能使用跨行缩进。这两种存储布局不要混用。