优先用 json.loads() 解析标准 JSON;若失败,单引号等 Python 字面量用 ast.literal_eval(),JS 风格(注释、NaN 等)用 json5.loads(),局部非法字段需提取后单独修复解析。

遇到 JSONDecodeError: Expecting property name enclosed in double quotes 时怎么办
Python 的 json.loads() 严格遵循 RFC 8259,只认双引号包裹的键名和字符串。现实中常碰到单引号、无引号键、尾随逗号、注释等“类 JSON”数据,直接解析必然抛出 JSONDecodeError。
别急着写正则替换——先判断数据来源:如果是人手写的配置片段、前端调试输出、日志行或某些低版本 API 响应,大概率是 JavaScript 对象字面量风格,这时用 ast.literal_eval() 更安全可靠:
import ast
<p>s = "{'name': 'Alice', 'scores': [95, 87], 'active': true}"
data = ast.literal_eval(s) # ✅ 返回 dict,支持单引号、true/false/None</p>-
ast.literal_eval()只允许基础 Python 字面量(dict、list、str、int、float、True、False、None),不执行任意代码,比eval()安全得多 - 不支持 JavaScript 特有语法:如
undefined、Infinity、八进制数字(0755)、尾随逗号(Python 3.8+ 的ast.literal_eval已支持,但旧版会报错) - 若含注释(
//或/* */),需先用正则粗略清理,例如:re.sub(r'//.*|/\*.*?\*/', '', s, flags=re.DOTALL)
需要处理尾随逗号、单双引号混用、NaN/Infinity 的场景
当数据来自 Node.js 输出、Postman 响应预览、或某些 CLI 工具 dump,可能含 NaN、Infinity、尾随逗号且引号不统一。此时 json5 库是最贴切的替代方案(它专为解析 JS 风格配置设计):
pip install json5
import json5
<p>s = '''
{
"id": 123,
"name": 'Bob', // 单引号
"tags": ["dev", "py"], // 尾随逗号可选
"value": NaN, // ✅ 支持
"limit": Infinity // ✅ 支持
}
'''
data = json5.loads(s)</p>-
json5.loads()兼容所有 JSON5 规范特性:单双引号、注释、尾随逗号、NaN/Infinity、十六进制(0xFF)、无引号键({foo: 42}) - 注意:它把
NaN解析为float('nan'),Infinity为float('inf'),后续做数值比较时需用math.isnan()而非== - 不支持 Python 特有字面量(如
None、True),所以不能替代ast.literal_eval()处理混合 Python/JS 风格数据
嵌套结构中部分字段是非法 JSON 字符串,需局部修复再解析
典型场景:API 返回一个标准 JSON 对象,但其中某个字段(如 metadata)的值是未转义的 HTML 片段或带单引号的 JS 对象字符串。硬解整个响应会失败,得“拆开修”:
立即学习“Python免费学习笔记(深入)”;
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
import json
import re
<p>raw = '''{"id": 1, "metadata": "{'user': 'admin', 'role': 'dev'}"}'''</p><h1>先解析外层合法 JSON</h1><p>outer = json.loads(raw)</p><h1>单独提取并修复 metadata 字段</h1><p>meta_str = outer["metadata"]</p><h1>简单修复:单引号 → 双引号,但需避开已存在的双引号内容(谨慎!)</h1><p>fixed_meta = meta_str.replace("'", '"')
try:
outer["metadata"] = json.loads(fixed_meta)
except json.JSONDecodeError:</p><h1>回退到 ast.literal_eval(更鲁棒)</h1><pre class='brush:python;toolbar:false;'>outer["metadata"] = ast.literal_eval(meta_str)- 永远优先尝试
json.loads()局部字段,失败再降级;不要一上来就对整条数据用ast.literal_eval(),否则会把数字、布尔值误转为字符串 - 用正则全局替换引号风险高(比如字符串内含
O'Reilly),仅适用于已知内容干净的场景;更稳妥的是用ast.literal_eval()或json5.loads() - 如果字段值本身是 Base64 编码或 URL 编码过的 JSON 字符串,务必先解码再解析,否则
json.loads()必然失败
性能敏感场景下如何选型
批量处理上万条非标 JSON 日志时,解析器选择直接影响耗时。实测(CPython 3.11,i7-11800H):
-
json.loads()(标准输入):≈ 0.8 μs/次 -
ast.literal_eval()(单引号 dict):≈ 3.2 μs/次 —— 比json慢约 4 倍,但远快于正则+替换+重试流程 -
json5.loads():≈ 15–20 μs/次 —— 功能强但开销明显,适合低频、交互式解析(如配置加载),不适合实时日志流
真正棘手的是混合格式:同一字段有时是标准 JSON,有时是单引号对象,有时是 null 字符串。这种情况下,封装一个带 fallback 的解析函数比反复判断更可靠,但要注意 fallback 链不能过长(比如 json → json5 → ast → regex cleanup → json),每多一层都增加不可控错误路径。

















