标准 JSON 规范明确禁止尾随逗号,所以当你用 json.loads('[1, 2, 3,]') 解析一段带有尾随逗号的数据时,Python 会毫不留情地抛出 json.decoder.JSONDecodeError: Expecting value 或 Expecting ',' delimiter。但现实往往是残酷的:日志系统导出的数组、第三方接口拼出来的字符串、甚至同事手写的配置文件,都可能出现 {"name": "test",} 这种写法。浏览器端的 JavaScript 早就容忍了这种写法,可 Python 标准库的态度非常坚决。这篇文章就来聊聊怎么优雅地让 Python 容忍尾随逗号。

先弄清楚错误是怎么发生的
在动手解决问题之前,先看看尾随逗号到底在哪个环节出问题。下面的代码模拟了最常见的两种情况:数组尾部多了一个逗号,以及字典最后一个键值对后面多了一个逗号。
import json
# 数组尾随逗号
try:
json.loads('[1, 2, 3,]')
except json.JSONDecodeError as e:
print("数组错误:", e.msg, "位置:", e.pos)
# 对象尾随逗号
try:
json.loads('{"name": "test",}')
except json.JSONDecodeError as e:
print("对象错误:", e.msg, "位置:", e.pos)运行后你会看到,数组的情况报的是 Expecting value,因为解析器在读完逗号后期待下一个元素,结果碰到了右中括号。对象的情况则报 Expecting property name enclosed in double quotes,原因类似——解析器期待下一个键名,却等来了右花括号。理解这一点很重要,因为它决定了后面的修复方案必须在语法层面做处理,而不是简单粗暴地删字符。
顺便澄清一个常见误解:json.loads 的 strict 参数跟尾随逗号没有任何关系。它的作用是控制是否允许字符串内部出现控制字符(比如换行符、制表符),默认 strict=True 时字符串里不能包含裸露的控制字符。传 strict=False 并不能解决尾随逗号问题,网上不少文章在这里含糊其辞,容易误导人。
方案一:正则预处理,动手删掉多余的逗号
最直接的思路是先把字符串里的尾随逗号删掉,再交给 json.loads 处理。关键在于正则要写得足够准确:既要匹配 ,] 也要匹配 ,},而且必须考虑到逗号和括号之间可能存在空白字符、换行符。
import json
import re
# 匹配逗号后面(可有空白)紧跟 ] 或 } 的情况
_TRAILING_COMMA = re.compile(r',(\s*[}\]])')
def tolerant_loads(text):
cleaned = _TRAILING_COMMA.sub(r'\1', text)
return json.loads(cleaned)
data = tolerant_loads('''
{
"users": [
{"id": 1, "name": "张三",},
{"id": 2, "name": "李四",},
],
"total": 2,
}
''')
print(data)这个方案最大的优点是不引入任何外部依赖,代码只有几行,对于偶尔需要处理脏数据的脚本来说非常合适。由于 json.loads 本身是用 C 实现的(_json 模块),预处理之后的解析速度依然很快。
但它也有一个不容忽视的隐患:正则无法理解 JSON 的语义。如果字符串值内部恰好包含了 ,] 这样的字面内容,比如 {"desc": "价格区间[10,20,]"},这条正则就会误伤,把字符串里面的内容也改掉,轻则数据被污染,重则解析结果直接错误。要规避这个问题,一个稳妥的做法是先尝试直接解析,失败了才做正则清洗:
def safe_loads(text):
try:
return json.loads(text) # 标准JSON直接走快速路径
except json.JSONDecodeError:
cleaned = _TRAILING_COMMA.sub(r'\1', text)
return json.loads(cleaned) # 清洗后再试一次这样标准数据完全不受正则影响,只有真正解析失败时才启用清洗逻辑,风险被压缩到最小。
方案二:借助第三方宽松解析库
如果脏数据的来源不可控,除了尾随逗号还可能有不带引号的键名、单引号字符串、注释等五花八门的问题,正则方案就力不从心了,这时候可以考虑专门的宽松解析库。
第一个推荐的是 json5,它实现了 JSON5 规范,天然支持尾随逗号、单引号、注释、十六进制数字等扩展语法,解析结果的 API 和标准 json 模块几乎一致,学习成本极低:
# pip install json5
import json5
data = json5.loads('''
{
// 支持注释
name: '张三', // 键名可以不带引号
tags: ['a', 'b', 'c',],
}
''')
print(data)第二个选择是 demjson3,它是老牌宽松解析库 demjson 的维护分支,容错能力很强,甚至能修复一些缺失引号、多余逗号混用的问题。它的 decode 方法还提供参数控制是否严格报错。不过 demjson3 是纯 Python 实现,数据量大时性能明显不如标准库,只适合处理配置文件这类小体量数据。
# pip install demjson3
import demjson3
# 非严格模式,容忍各种小毛病
data = demjson3.decode('{"name": "test",}')
print(data)还有 dirty-json 这个库,专门为解析“脏”JSON 设计,对 JavaScript 对象字面量的兼容性不错。三个库的取舍大致是:数据来源接近 JSON5 规范就用 json5;数据来源是 JavaScript 代码片段、格式比较随意就用 demjson3 或 dirty-json;追求性能且问题仅限于尾随逗号,就用前面的正则方案。
方案三:从源头解决,别让脏数据产生
坦白说,前面两种方案都属于“下游兜底”,治标不治本。尾随逗号之所以存在,通常是因为数据生产方用了字符串拼接而不是序列化工具。如果生产方也是你的代码,最好的办法是让对方统一用 json.dumps 生成数据,从根上保证格式合法。
如果数据源无法控制(比如第三方系统、历史日志),建议在接收层统一封装一个入口函数,所有外部 JSON 都经过它进来,内部实现 try/except 加降级逻辑,并记录解析失败的原始数据用于排查。一个工程化的写法如下:
import json
import re
import logging
logger = logging.getLogger(__name__)
def robust_json_loads(text, default=None):
if not isinstance(text, str):
return default
try:
return json.loads(text)
except json.JSONDecodeError as e:
logger.warning("JSON解析失败,尝试宽松清洗: %s, 原文: %.200s", e, text)
try:
cleaned = re.sub(r',(\s*[}\]])', r'\1', text)
return json.loads(cleaned)
except json.JSONDecodeError:
logger.error("清洗后仍无法解析,返回默认值")
return default这个函数有三个层次的保护:类型检查挡住非字符串输入,快速路径处理标准 JSON,清洗降级处理尾随逗号,最后还有默认值兜底。配合日志记录,即使出现意料之外的格式问题,也能快速定位到原始数据。
总结一下选型建议:临时脚本用正则清洗就够了;应用层长期处理外部脏数据就封装统一的入口函数;数据格式复杂多变就直接上 json5。切记别指望 strict=False 能解决问题,那是字符串控制字符的开关,跟尾随逗号半点关系都没有。
json.loads尾随逗号Python JSON解析修改时间:2026-09-05 10:56:39