首页 / 资讯中心 / 文章详情

深入浅出Python中的JSON操作和实践

深入浅出Python中的JSON操作和实践 ★ FEATURED ARTICLE
前言json是标准库里最看起来简单、实际最容易写错细节的模块之一。加载、转储两个函数就够用——直到你遇到中文变成\uXXXX、datetime对象报TypeError、读文件时编码不对导致解析失败、同一个键在 JSON 里出现两次却只留下一个。本文把json模块的关键参数逐个讲清重点放在那些默认行为不符合直觉的地方ensure_ascii默认是True中文会被转义、indent会顺带改变分隔符、default是处理不可序列化对象的正确钩子、object_pairs_hook能拿到重复键的原始对。同时点破两个 JSON 格式本身的限制没有注释也没有日期类型。代码都基于 Python 3本机没有解释器只能逐行人工推演不声称运行过。一、四个入口与它们的签名模块对外只有四个常用函数两两对称# 适用于 Python 3.8import json# 对象 - 字符串s json.dumps({a: 1}) # dict - strobj json.loads(s) # str/bytes/bytearray - Python 对象# 对象 - 文件fp 需要有 write / read 方法with open(data.json, w, encodingutf-8) as f:json.dump({a: 1}, f)with open(data.json, r, encodingutf-8) as f:obj json.load(f)必须记住dumps/loads带sstringdump/load不带s操作的是文件对象。这对命名是最常见的笔误来源。dumps的完整参数表关键字专用是skipkeys、ensure_ascii、check_circular、allow_nan、cls、indent、separators、default、sort_keys。loads则是cls、object_hook、parse_float、parse_int、parse_constant、object_pairs_hook。从 3.6 起所有可选参数都是关键字专用位置传参会报错。二、ensure_ascii中文为什么会变样ensure_ascii的默认值是True。这意味着默认情况下所有非 ASCII 字符都会被转义成\uXXXX形式。# 适用于 Python 3.8import jsondata {城市: 北京}print(json.dumps(data))# 默认输出里城市北京会变成 \u 转义序列print(json.dumps(data, ensure_asciiFalse))# 传 False 才会原样保留中文字符转义本身不改变语义——json.loads能把两种形式都还原成同样的 Python 字符串。所以这不是错误只影响可读性和文件体积。但如果你要把 JSON 给人看或者给需要可读文本的下游用就该显式传ensure_asciiFalse写文件时还要保证文件是以 UTF-8 打开否则可能因为编码不匹配而写入失败。# 适用于 Python 3.8import jsonwith open(out.json, w, encodingutf-8) as f:json.dump({城市: 北京}, f, ensure_asciiFalse, indent2)注意open里那个encodingutf-8不写它就会用平台默认编码在 Windows 上通常不是 UTF-8于是中文可能写坏、读回来json.load直接抛JSONDecodeError这是json模块在解析失败时抛出的异常类型。跨平台代码里编码一定要显式写。三、格式化indent、sort_keys与separatorsindent可以让输出带上换行与缩进方便人读。它接受正整数缩进这么多空格也接受字符串比如\t。传None默认则不换行。有一个容易被忽略的联动separators的默认值取决于indent。当indent为None时默认分隔符是紧凑的(, , : )当indent不是None时默认分隔符变成(,, : )——即逗号后不再加空格避免行尾多出空白。如果你为了减小体积手动传了separators(,, :)那就完全紧凑、没有空格。sort_keysTrue会让字典按键排序后输出。默认是False也就是保持插入顺序。官方文档明确说明json模块的编码器与解码器默认保持输入输出顺序只有底层容器本身无序时顺序才会丢失。这一点和 Python 的字典有关从 3.7 起字典保证保持插入顺序3.6 是实现细节不要依赖。所以在 3.7 及以上dumps不排序也会原样保留键顺序。注意sort_keys是按转换后的字符串键排序而不是按原始键类型排序——如果键是数字排序结果可能和你预期的不一样。参数默认值作用常见误用ensure_asciiTrue是否转义非 ASCII以为默认输出中文indentNone缩进int 或 str以为只影响缩进忽略它改变分隔符sort_keysFalse按键排序输出在 3.7 上误以为必须开才能保序defaultNone不可序列化对象的兜底函数误当成object_hook的搭档separators随indent变控制项与项之间的分隔符手写后与缩进风格冲突四、default处理datetime等不可序列化对象JSON 的类型只有对象、数组、字符串、数字、true/false、null。没有日期类型。这是 JSON 格式本身的限制不是 Python 的。所以datetime、date、Decimal、自定义类实例json.dumps都不认识默认会抛TypeError。官方文档给出的扩展方式有两种方式一传default回调。当编码器遇到不能序列化的对象时调用这个函数它的返回值会被当作可序列化的对象继续处理。如果它也无法处理应当抛出TypeError。# 适用于 Python 3.8import datetimeimport jsondef json_default(obj):if isinstance(obj, (datetime.datetime, datetime.date)):return obj.isoformat() # 转成 ISO 8601 字符串if isinstance(obj, set):return sorted(obj)raise TypeError(f无法序列化的类型: {type(obj).__name__})payload {when: datetime.datetime(2026, 10, 7, 12, 0, 0)}print(json.dumps(payload, defaultjson_default, ensure_asciiFalse))方式二继承json.JSONEncoder并覆写default方法。这是cls参数的用途。仅当需要复用多处都按这套规则编码时才值得单点使用直接用default更省事。反序列化时的对应问题是isoformat()出来的字符串读回来还是字符串不会自动变回datetime。要还原就得自己解析比如用datetime.fromisoformat注意它接受的格式范围随版本扩展过。JSON 里没有类型标注谁转成字符串的、按什么格式转的只能靠约定。五、object_pairs_hook与重复键JSON 标准允许对象里出现重复的键虽然不推荐。Python 的json.loads默认把它们塞进dict后出现的覆盖先出现的前面的静默丢失。如果你想看到原始键值对序列用object_pairs_hook它接收一个由键值对组成的列表返回你构造的对象。官方文档指出如果同时设置了object_hookobject_pairs_hook优先。# 适用于 Python 3.8import jsondef no_dup_pairs(pairs):检测重复键有重复就抛错拒绝静默覆盖。seen {}for key, value in pairs:if key in seen:raise ValueError(fJSON 中存在重复键: {key!r})seen[key] valuereturn seenraw {a: 1, a: 2}print(json.loads(raw)) # 后值覆盖前值print(json.loads(raw, object_pairs_hookno_dup_pairs)) # 抛错暴露问题这个钩子的另一个用途是保序地构造有序结构——虽然 3.7 起字典已保序这个用途的必要性下降了但检测重复键的价值依然存在。对外部数据做严格校验时重复键往往是异常信号。六、数字大整数与浮点精度JSON 的数字不带类型标注。json解码时默认这样映射带小数点或指数的解析成float否则解析成int。两个要留意的地方大整数。Python 的int是任意精度的但出于安全考虑从 3.11 起默认的parse_int会对整数字符串的长度设限利用解释器自身的整数字符串转换长度限制以避免超长数字造成的拒绝服务攻击。如果你确实要处理超长整数需要显式传自己的parse_int。浮点精度。JSON 里的0.1解析成 Python 的float而float是二进制浮点无法精确表示十进制的 0.1。所以json.loads(0.1) json.loads(0.2)不会等于json.loads(0.3)——这是浮点本身的实现特性不是json的 bug。要精确的十进制运算可以让parse_float用decimal.Decimal来构造。# 适用于 Python 3.8import decimalimport jsonprint(json.loads(0.1) json.loads(0.2) json.loads(0.3)) # Falseexact json.loads(0.1, parse_floatdecimal.Decimal)print(exact, type(exact).__name__)另外json.dumps默认allow_nanTrue会把float(nan)、float(inf)写成NaN、Infinity。这些不是合法的 JSON。如果输出要交给严格的其他语言解析器应传allow_nanFalse让它在遇到这些值时直接报错。七、JSON 的两个天生缺失注释与类型JSON 没有注释。官方规范里根本不支持注释。所以{a: 1} // 说明这种写法解析会失败。需要配置里带注释应当换成别的格式比如tomllib能读的 TOML从 3.11 起进入标准库而不是自己用正则去删注释再解析——那种做法会把字符串里的//也误删。JSON 没有日期、没有二进制、没有自定义类型。这些都必须先转成字符串或经过default转换。转换规则由你和你的下游共同约定JSON 不提供任何元信息。常见坑点以为dumps默认输出中文。❌json.dumps({城市: 北京})后抱怨中文全变成了\u转义。 ✅ensure_ascii默认为True要保留原文需传ensure_asciiFalse并保证文件以 UTF-8 打开。open不写encoding。❌ 读写 JSON 文件时不指定编码在 Windows 上读写中文出错或解析失败。 ✅ 读写都显式写encodingutf-8json.load遇到非法文档会抛JSONDecodeError。把default当成反序列化的钩子。❌ 期待用default在读取时把字符串还原成datetime。 ✅default只在编码阶段处理不可序列化对象反序列化的还原要靠object_hook或自己解析。给 JSON 加注释。❌ 在.json文件里写//或#注释再让程序解析。 ✅ JSON 格式不支持注释配置类数据改用支持注释的格式不要用正则剥注释再解析。以为indent只影响缩进。❌ 设了indent2后发现行尾多了空格或没有空格与自己手写的separators冲突。 ✅ 记住indent不为None时默认分隔符会变成无空格的紧凑形式需要精确控制就显式传separators。在 3.7 上仍用sort_keys来保证顺序。❌ 认为不排序键顺序就会乱。 ✅ 3.7 起字典保证插入顺序json也保持顺序sort_keys的用途是让输出便于比对而非保序。忽略重复键被静默覆盖。❌ 对外部数据直接json.loads同一键出现两次时前面那份悄悄丢了。 ✅ 用object_pairs_hook检测重复键并报错注意它优先于object_hook。把NaN/Infinity当成合法 JSON。❌ 默认序列化结果里带NaN再交给别的语言的解析器读取时失败。 ✅ 这类值不是合法 JSON需要严格输出时传allow_nanFalse提前暴露问题。总结需求做法关键参数中文可读输出关掉 ASCII 转义ensure_asciiFalse格式化输出缩进 可选排序indent、sort_keys、separators序列化日期/自定义对象编码阶段的兜底default或覆写JSONEncoder检测重复键拿到原始键值对object_pairs_hook高精度十进制替换浮点解析parse_floatdecimal.Decimal严格 IO 校验拒绝非法字面量allow_nanFalsejson模块的简单是一种错觉函数只有四个参数只有十来个但每一个默认值背后都有取舍。记住三句话就够了——中文默认会被转义、日期必须自己转换、JSON 里没有注释也没有日期类型。剩下的细节请以json模块官方文档的当前版本为准。
阅读完成 · 觉得有帮助?
咨询建站