☰
Python JSONDecodeError报错解析:Expecting ‘,‘ delimiter 排查与修复指南
2026/10/12 23:56:56 网站建设 项目流程

1. 从一个让人抓狂的报错说起

json.decoder.JSONDecodeError: Expecting ',' delimiter: line 1 column 1780 (char 17——如果你正在读这篇文章,大概率是刚被这行红字糊了一脸。它不像语法错误那样直接告诉你哪一行写错了,也不像文件找不到那样干脆利落,它给你一个列号、一个字符偏移量,然后让你自己去猜:到底是哪个逗号没写?还是哪个引号没配对?

我在实际项目里处理过大量JSON配置文件、接口返回数据和日志文件,这个报错出现的频率能排进前三。它最坑的地方在于:报错位置往往不是真正出错的位置。比如它说第1780列有问题,但真正的错误可能在第1700列——因为前面少了一个引号,解析器一路"吞"到后面才发现不对劲。

这篇文章会把这个报错彻底拆开讲清楚:它到底在说什么、为什么会出现、怎么快速定位、怎么一次性修好、以及怎么在代码层面做好防御。不管你是刚学Python的新手,还是已经写过不少项目的老手,都能从里面找到能直接用的排查思路和修复方案。

2. 这个报错到底在说什么

2.1 逐词拆解报错信息

先把这个报错信息拆成几块来看:

  • json.decoder.JSONDecodeError:这是Python标准库json模块在解析时抛出的异常类型,说明问题出在解码阶段,也就是把字符串或文件内容转成Python对象的过程中。
  • Expecting ',' delimiter:解析器期望在这里看到一个逗号,但实际看到的不是逗号。这是最核心的提示。
  • line 1 column 1780 (char 17:位置信息。line 1说明整个JSON内容被当成了一行(常见于压缩过的JSON或从接口拿到的单行字符串),column 1780是第1780个字符的位置,char 17是字符偏移量。

这里有个容易混淆的点:column和char的区别。在Python的json模块里,column是从1开始计数的列号,char是从0开始的字符索引。所以column 1780对应的就是char 1779。但报错里显示char 17,这通常意味着报错信息被截断了,或者你看到的是一段被裁剪过的错误输出。

提示:如果你在终端里看到这个报错,建议把完整的错误信息复制出来,不要只看最后一行。完整的traceback会告诉你是在哪一行代码调用的json.loads()或json.load()。

2.2 为什么解析器会"期望一个逗号"

JSON的语法规则非常严格。在一个对象(用{}包裹)里,键值对之间必须用逗号分隔;在一个数组(用[]包裹)里,元素之间也必须用逗号分隔。解析器是逐字符扫描的,当它解析完一个完整的值(比如一个字符串、一个数字、一个对象)之后,它会预期下一个有效字符要么是逗号(表示还有下一个元素),要么是闭合符号(}或],表示结束了)。

如果它看到的既不是逗号也不是闭合符号,就会抛出Expecting ',' delimiter。换句话说,这个报错的意思是:我读完了上一个值,现在不知道该继续还是该结束,而你给我的字符让我很困惑。

2.3 报错位置为什么经常"指错地方"

这是最需要理解的一点。JSON解析器是贪婪匹配的:它会尽可能地把当前的值读完。比如遇到一个引号,它会一直读到下一个匹配的引号才认为字符串结束。如果中间少了一个转义,或者引号用错了(比如用了中文引号),解析器可能会把后面很远的内容都当成字符串的一部分,直到遇到下一个引号才"醒过来"。这时候它发现后面跟着的东西不符合预期,就报错了——但报错位置已经离真正的错误很远了。

举个简单的例子:

{ "name": "张三, "age": 25 }

这里"张三后面少了一个闭合引号。解析器会一直往后读,把,\n "age"都当成字符串内容,直到遇到25后面的引号才结束。然后它发现后面是:,不是逗号也不是},于是报错。报错位置可能在age那一行,但真正的问题在name那一行。

3. 六种最常见的触发场景

3.1 中文标点混入:最隐蔽的坑

这是国内开发者最常踩的坑,没有之一。中文输入法下打出的引号""、逗号,、冒号:,在视觉上和英文标点几乎一样,但在JSON里是完全非法的字符。

{ "name":"张三", "age":25 }

上面这段JSON里,冒号是中文的:,逗号是中文的,,引号也是中文的""。解析器遇到第一个中文冒号就会懵掉。这种问题在手动编辑配置文件时特别容易出现,尤其是从文档里复制粘贴的时候。

排查方法:用编辑器的"显示不可见字符"功能,或者直接用Python检查:

with open('config.json', 'r', encoding='utf-8') as f: content = f.read() # 检查常见的中文标点 chinese_punctuation = [',', ':', '“', '”', '‘', '’', ';', '(', ')'] for i, char in enumerate(content): if char in chinese_punctuation: print(f"位置 {i}: 发现中文标点 '{char}'")

3.2 多余的尾逗号:JavaScript习惯的代价

JavaScript允许对象和数组的最后一个元素后面带逗号(trailing comma),但JSON标准严格禁止这一点。

{ "name": "张三", "age": 25, }

上面这段在JavaScript里完全合法,但在JSON里,25后面的逗号会让解析器在遇到}时期望另一个键值对,从而报错。

修复方法:删掉最后一个逗号。如果是程序生成的JSON,检查生成逻辑里是否有join操作多加了分隔符。

3.3 字符串内未转义的特殊字符

JSON字符串里如果包含双引号、反斜杠、换行符等,必须转义。未转义的双引号会提前结束字符串,导致后面的内容被误解析。

{ "message": "他说:"你好"" }

这里他说:后面的引号提前结束了字符串,解析器接着看到你好,不知道该怎么处理。

正确写法:

{ "message": "他说:\"你好\"" }

3.4 单引号代替双引号

JSON标准规定字符串必须用双引号,单引号是非法的。但Python的字典字面量允许单引号,很多人会把Python字典直接当JSON用。

# 这是Python字典,不是JSON data = {'name': '张三', 'age': 25}

如果你把str(data)的结果直接当JSON解析,就会报错。正确做法是用json.dumps():

import json data = {'name': '张三', 'age': 25} json_str = json.dumps(data, ensure_ascii=False)

3.5 文件编码问题导致的字符错乱

如果JSON文件本身是GBK编码,但你用UTF-8去读,或者反过来,中文字符会变成乱码,乱码字符可能恰好落在引号或逗号的位置上,导致解析失败。

排查方法:

import chardet with open('data.json', 'rb') as f: raw = f.read() result = chardet.detect(raw) print(result) # {'encoding': 'utf-8', 'confidence': 0.99}

3.6 嵌套结构中的括号不匹配

JSON的括号必须严格配对。少一个}或多一个],都会导致解析器在错误的位置期望逗号。

{ "users": [ {"name": "张三", "age": 25}, {"name": "李四", "age": 30} ]

上面这段少了一个闭合的},解析器会在数组结束后期望逗号,但看到的是文件结尾。

4. 三步定位法:快速找到真正的错误位置

4.1 第一步:用在线工具或编辑器做语法校验

不要一上来就盯着报错位置看。先把JSON内容复制到支持JSON校验的编辑器里(比如VS Code、Sublime Text),或者用在线JSON校验工具。这些工具通常会直接标红出错的位置,比Python的报错信息直观得多。

VS Code里如果安装了JSON插件,打开.json文件时会自动校验,错误位置会有波浪线提示。如果是字符串形式的JSON,可以新建一个临时文件粘贴进去看。

4.2 第二步:用Python逐段缩小范围

如果JSON内容很长,在线工具也不好定位,可以用"二分法"缩小范围。把JSON内容分成两半,分别尝试解析,看哪一半报错。

import json def find_error_location(json_str): """二分法定位JSON错误位置""" try: json.loads(json_str) return "JSON合法" except json.JSONDecodeError as e: return f"错误位置: line {e.lineno}, column {e.colno}, msg: {e.msg}" # 假设content是完整的JSON字符串 mid = len(content) // 2 # 尝试解析前半部分(需要补全括号才能解析,这里只是示意思路)

更实用的方法是逐行解析。如果JSON是多行的,可以逐行累加,看加到哪一行时报错:

import json def locate_error_by_lines(filepath): with open(filepath, 'r', encoding='utf-8') as f: lines = f.readlines() for i in range(1, len(lines) + 1): partial = ''.join(lines[:i]) try: json.loads(partial) except json.JSONDecodeError as e: if 'Expecting' in e.msg and i < len(lines): # 可能还没到真正的错误行,继续 continue print(f"累加到第 {i} 行时报错: {e}") print(f"出错行内容: {lines[i-1].rstrip()}") break

4.3 第三步:打印报错位置前后的上下文

Python的JSONDecodeError对象有pos属性,可以直接拿到出错字符的索引。用这个索引打印前后各50个字符,通常能一眼看出问题。

import json def debug_json_error(json_str): try: json.loads(json_str) print("JSON解析成功") except json.JSONDecodeError as e: pos = e.pos start = max(0, pos - 50) end = min(len(json_str), pos + 50) print(f"错误位置: {pos}") print(f"错误信息: {e.msg}") print(f"上下文: ...{json_str[start:pos]}【这里出错】{json_str[pos:end]}...") # 打印出错位置附近的字符及其Unicode码点 for i in range(max(0, pos-5), min(len(json_str), pos+5)): char = json_str[i] print(f" 位置 {i}: '{char}' (U+{ord(char):04X})") # 使用示例 with open('problem.json', 'r', encoding='utf-8') as f: content = f.read() debug_json_error(content)

这段代码会打印出错位置附近的字符和它们的Unicode码点。如果看到U+FF0C(中文逗号)或U+201C(中文左引号),问题就一目了然了。

5. 修复方案与代码层面的防御

5.1 手动修复:针对不同场景的处理

场景一:中文标点。用编辑器的查找替换功能,把中文标点批量替换成英文标点。注意要逐个确认,不要盲目全局替换,因为字符串内容里可能合法地包含中文标点。

场景二:尾逗号。用正则表达式匹配并删除:

import re def remove_trailing_commas(json_str): # 删除对象或数组最后一个元素后的逗号 json_str = re.sub(r',(\s*[}\]])', r'\1', json_str) return json_str

场景三:单引号。如果JSON是用Python字典的str()生成的,改用json.dumps()重新生成。如果必须处理单引号JSON,可以用ast.literal_eval()先转成Python对象,再json.dumps():

import ast import json single_quote_json = "{'name': '张三', 'age': 25}" python_dict = ast.literal_eval(single_quote_json) valid_json = json.dumps(python_dict, ensure_ascii=False) print(valid_json) # {"name": "张三", "age": 25}

注意:ast.literal_eval()只适用于简单的Python字面量,如果内容里有true、false、null这些JSON特有的值,需要先替换成Python的True、False、None。

5.2 读取文件时的编码防御

永远不要假设JSON文件的编码。用chardet检测,或者统一用utf-8-sig读取(可以自动处理BOM头):

import json def safe_load_json(filepath): encodings = ['utf-8-sig', 'utf-8', 'gbk', 'gb2312', 'latin-1'] for enc in encodings: try: with open(filepath, 'r', encoding=enc) as f: return json.load(f) except (UnicodeDecodeError, json.JSONDecodeError): continue raise ValueError(f"无法解析文件: {filepath}")

5.3 解析前的预处理管道

在实际项目里,我通常会建一个预处理函数,在json.loads()之前做几件事:

import json import re def preprocess_json(json_str): """JSON解析前的预处理""" # 1. 去除BOM头 if json_str.startswith('\ufeff'): json_str = json_str[1:] # 2. 替换中文标点(仅在非字符串区域,简单场景可直接替换) # 注意:这个操作有风险,字符串内容里的中文标点会被误伤 # 更安全的做法是用状态机判断是否在字符串内 # 3. 去除尾逗号 json_str = re.sub(r',(\s*[}\]])', r'\1', json_str) # 4. 去除注释(JSON标准不支持注释) json_str = re.sub(r'//.*?\n', '\n', json_str) json_str = re.sub(r'/\*.*?\*/', '', json_str, flags=re.DOTALL) return json_str def robust_json_loads(json_str): """健壮的JSON解析""" try: return json.loads(json_str) except json.JSONDecodeError as e: # 尝试预处理后再解析 processed = preprocess_json(json_str) try: return json.loads(processed) except json.JSONDecodeError: # 预处理也失败,抛出原始错误并附带上下文 raise e

提示:预处理管道是"最后手段",不要把它当成常规做法。最好的策略是从源头保证JSON的合法性,而不是在解析时各种修补。

5.4 用JSON Schema做结构校验

如果JSON来自外部接口或用户输入,光靠json.loads()只能保证语法正确,不能保证结构符合预期。用jsonschema库可以做更严格的校验:

import json from jsonschema import validate, ValidationError schema = { "type": "object", "properties": { "name": {"type": "string"}, "age": {"type": "integer", "minimum": 0} }, "required": ["name", "age"] } data = json.loads(json_str) try: validate(instance=data, schema=schema) print("结构校验通过") except ValidationError as e: print(f"结构校验失败: {e.message}")

6. 几个容易被忽略的细节

6.1 大文件解析的内存问题

如果JSON文件有几百MB甚至上GB,json.load()会一次性把整个文件读进内存,可能导致内存溢出。这时候可以用ijson库做流式解析:

import ijson with open('large_file.json', 'rb') as f: parser = ijson.items(f, 'item') for obj in parser: process(obj)

ijson不会一次性加载整个文件,而是逐个解析数组元素或对象属性,适合处理日志、大数据导出等场景。

6.2 浮点数精度丢失

JSON解析浮点数时,Python默认用float,会有精度问题。比如0.1 + 0.2在JSON里存的是0.30000000000000004。如果涉及金额计算,建议用decimal.Decimal:

import json from decimal import Decimal data = json.loads('{"price": 19.99}', parse_float=Decimal) print(data['price']) # Decimal('19.99')

6.3 重复键的处理

JSON标准没有明确规定重复键的行为,Python的json模块默认保留最后一个值:

import json data = json.loads('{"name": "张三", "name": "李四"}') print(data) # {'name': '李四'}

如果业务上不允许重复键,需要自己写object_pairs_hook来检测:

import json def check_duplicate_keys(pairs): seen = set() for key, value in pairs: if key in seen: raise ValueError(f"重复的键: {key}") seen.add(key) return dict(pairs) data = json.loads(json_str, object_pairs_hook=check_duplicate_keys)

6.4 报错信息里的char偏移量怎么用

前面提到char是从0开始的字符索引。如果你拿到了完整的报错信息,可以直接用这个索引去切片:

import json json_str = '...' # 你的JSON内容 try: json.loads(json_str) except json.JSONDecodeError as e: # e.pos 就是char偏移量 print(f"出错字符: '{json_str[e.pos]}'") print(f"前后文: {json_str[max(0,e.pos-30):e.pos+30]}")

但要注意:如果JSON内容里有Unicode字符(比如中文),char偏移量是按字符算的,不是按字节算的。所以用json_str[e.pos]能准确拿到出错的那个字符。

7. 我踩过的几个真实坑

第一个坑是BOM头。有次从Windows系统导出的JSON文件,开头带了\ufeff,Python用utf-8读取时不会自动去掉,导致json.loads()在第一个字符就报错。后来统一用utf-8-sig编码读取,问题解决。

第二个坑是接口返回的JSON里混了HTML。有些接口在出错时会返回一段HTML错误页面,但状态码还是200。直接response.json()就会报JSONDecodeError。后来加了判断:先检查Content-Type是不是application/json,不是的话打印原始内容再决定怎么处理。

第三个坑是多行字符串里的换行符。JSON标准不允许字符串里有未转义的换行符,但有些系统生成的JSON会直接换行。这种问题用正则很难修,最后是用json.loads()的strict=False参数解决的:

import json data = json.loads(json_str, strict=False)

strict=False允许字符串里出现控制字符(包括换行符),但这不是标准JSON,只适合处理内部数据。

8. 写JSON时的几个好习惯

与其在解析时报错,不如在生成时就保证合法。我总结了几条习惯:

  • 永远用json.dumps()生成JSON,不要手动拼接字符串。手动拼接几乎必然会在某个边界条件下出错。
  • 写入文件时指定ensure_ascii=False和encoding='utf-8',避免中文被转义成\uXXXX,也避免编码问题。
  • 用indent=2让JSON格式化输出,方便人工检查。虽然文件会大一点,但排查问题时省下的时间远超这点存储成本。
  • 如果JSON里包含用户输入的内容,一定要做转义。json.dumps()会自动处理,但如果你在字符串模板里嵌入JSON,就要格外小心。
  • 在CI流程里加一步JSON校验,所有.json文件在提交前都过一遍json.load(),把问题挡在代码合并之前。
import json import sys from pathlib import Path def validate_all_json(root_dir): errors = [] for path in Path(root_dir).rglob('*.json'): try: with open(path, 'r', encoding='utf-8-sig') as f: json.load(f) except json.JSONDecodeError as e: errors.append(f"{path}: {e}") if errors: print("发现JSON格式错误:") for err in errors: print(f" - {err}") sys.exit(1) print("所有JSON文件校验通过") if __name__ == '__main__': validate_all_json('.')

这个脚本可以直接放进项目的pre-commit钩子里,每次提交前自动跑一遍。我现在的项目里都配了这个,JSON相关的报错基本绝迹了。

处理JSONDecodeError的核心思路就一条:不要相信报错位置,要相信上下文。拿到报错后,先用工具或代码打印出错位置前后的内容,看清楚到底哪个字符不对劲,然后再决定是修数据还是修代码。大部分情况下,问题都出在中文标点、尾逗号、引号不匹配这三类上,把这几类排查完,90%的报错都能解决。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询