最近在对接第三方API时,我遇到了一个让人头疼的问题:明明看起来是JSON格式的字符串,用json.loads()解析时却总是报错。错误信息显示"Expecting property name enclosed in double quotes",意思是JSON属性名需要用双引号包裹。这让我意识到,很多开发者在使用Python处理JSON数据时都会遇到类似问题,特别是当数据来源不可控时。
JSON作为一种轻量级的数据交换格式,在Web开发、API交互、配置文件等场景中无处不在。Python内置的json模块虽然强大,但对JSON格式的规范性要求非常严格。根据RFC 8259规范,合法的JSON字符串必须使用双引号表示属性名和字符串值,而单引号或没有引号的属性名都会导致解析失败。
JSON的官方规范明确要求属性名必须使用双引号包裹。这种严格性源于JSON的设计初衷——作为一种可预测、无歧义的数据交换格式。Python的json模块严格遵循这一规范,因此当我们尝试解析{'status':404}这样的字符串时,就会遇到JSONDecodeError。
有趣的是,Python本身的字典语法允许使用单引号,这导致很多开发者会误以为JSON也支持这种写法。我曾经就犯过这样的错误,把一个Python字典直接str()转换后当作JSON字符串使用,结果当然是以失败告终。
在实际开发中,不规范JSON的来源主要有以下几种:
对于简单的JSON字符串,最直接的修复方法就是替换单引号为双引号:
python复制import json
bad_json = "{'status': 404}"
fixed_json = bad_json.replace("'", '"')
data = json.loads(fixed_json)
这种方法适用于大多数简单场景,但有几个注意事项:
{status:404})Python的ast模块提供了literal_eval方法,可以安全地评估包含Python字面量的字符串:
python复制import ast
bad_json = "{'status': 404}"
data = ast.literal_eval(bad_json)
# 然后可以再将data转为合法JSON
valid_json = json.dumps(data)
这种方法的优点是:
eval()安全得多缺点是:
json.loads()稍差对于更复杂的情况,可以使用正则表达式进行智能替换:
python复制import re
import json
bad_json = "{status: 404, 'message': 'It\\'s broken'}"
# 匹配属性名并添加双引号
fixed_json = re.sub(r"(['\"]?)(\w+)\1\s*:", r'"\2":', bad_json)
data = json.loads(fixed_json)
这个正则表达式会:
最好的防御就是从一开始就生成规范的JSON。Python的json.dumps()方法会自动处理所有规范问题:
python复制import json
data = {'status': 404}
valid_json = json.dumps(data) # 输出: {"status": 404}
当字典中包含日期、Decimal等非JSON原生类型时,需要自定义编码器:
python复制from datetime import datetime
import json
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
data = {'time': datetime.now()}
valid_json = json.dumps(data, cls=CustomEncoder)
json.dumps()提供了一些有用的参数来优化输出:
ensure_ascii=False:允许非ASCII字符indent=2:美化输出,便于阅读separators=(',', ':'):压缩输出体积有时候我们会遇到包含元组、集合等非JSON标准结构的伪JSON字符串:
python复制bad_json = "{'scores': (['math', 90], ['physics', 85])}"
修复这类数据需要多步处理:
()转换为列表[]None替换为JSON的nullpython复制fixed_json = (bad_json.replace("'", '"')
.replace("(", "[")
.replace(")", "]")
.replace("None", "null"))
data = json.loads(fixed_json)
当需要处理大量不规范JSON时,性能成为关键考虑因素。以下是一些优化建议:
str.translate()代替多次str.replace()进行批量字符替换ujson或orjson等第三方高性能JSON库python复制# 使用str.translate进行高效字符替换
trans_table = str.maketrans({"'": '"', "(": "[", ")": "]"})
fixed_json = bad_json.translate(trans_table)
健壮的生产代码应该妥善处理各种不规范JSON情况:
python复制import logging
def safe_json_parse(json_str):
try:
return json.loads(json_str)
except json.JSONDecodeError as e:
logging.warning(f"Failed to parse JSON: {e}")
try:
fixed = json_str.replace("'", '"')
return json.loads(fixed)
except json.JSONDecodeError:
logging.error("Unable to repair JSON string")
return None
在实际项目中,我通常会创建一个专门的JSON工具模块,集中处理各种边缘情况和异常。这样既保证了代码复用,又能统一处理逻辑。记住,处理外部数据时永远不要相信输入是规范的,防御性编程是关键。