Watch at Ease看见世界,读懂生活。

科技 · 实用指南

Python出现JSONDecodeError: Extra data?先确认记录边界再解析

第一个JSON值可能有效,但后面仍有内容。先保留原始字节,分清单文档、JSON Lines或连续值,再验证完整输入。

原创编辑插画:比较单个JSON文档、JSON Lines与连续JSON值。
原创编辑插画:比较单个JSON文档、JSON Lines与连续JSON值。 Watch at Ease

Extra data表示Python已经解出一个完整JSON值,随后又遇到非空白内容。它本身不能证明文件就是JSON Lines,也不能证明第二行一定是安全记录。选择解析器或改写文件前,先保存原始字节并确认生产端的记录边界。

保留证据并判断尾部形态

试验前复制或哈希原文件。记录输入编码、是否有BOM、异常消息、pos、lineno、colno,以及失败位置附近的转义预览。不要删除空白、全局替换}{或直接给原文套方括号,这些做法可能改变字符串、掩盖损坏或制造分隔符。

对解码文本使用JSONDecoder.raw_decode,找出第一个值结束的位置,再只检查剩余内容。若只剩空白,就是单文档;若另一个值开始,应确认生产端承诺的是每行一个完整值、无分隔连续值,还是误附加了无关字节。解析器由协议决定,不由报错行号决定。

按记录协议选择解析器

单个JSON文档应使用json.load或json.loads严格检查;顶层对象、数组、字符串、数字、布尔值或null都算一个值,无法解释的尾部内容应继续报错。若你控制一个集合,单个顶层数组是兼容性最高的JSON表示。

只有每个物理行都恰好是一个完整JSON值时,才按UTF-8逐行解析JSON Lines。对象、数组和标量都可以是记录,但空行不可以。跨多行的美化对象不是一条JSON Lines记录,逐行解析会失败。

不要猜分隔符,逐个消费连续值

当生产端确实输出无固定换行边界的连续JSON值时,可用跳过空白的raw_decode循环,取得每个值及其结束字符索引。每条记录都要检查预期类型或模式,失败时保留记录号与起始索引,最后还要确认跳过空白后精确到EOF。

raw_decode接收的是以JSON文档开头的字符串,并不会自动成为有界流式读取器。大文件需要经过设计的缓冲方案或审阅过的流式解析器。不要全局把}{替换为},{,这些字符可能出现在字符串里,真正损坏的边界也应明确拒绝。

把字符位置映射回字节,并在转换前验收

Python文档把JSONDecodeError.pos定义为doc中的起始索引,lineno和colno从解码后的字符串计算。我们的Python 3.12.14非ASCII样例在pos 7失败,但第二个值之前有8个UTF-8字节。可用len(text[:pos].encode("utf-8"))换算;前文含非ASCII时,绝不能直接把pos当二进制seek偏移。

同一测试中,真正JSON Lines在第2行第1列报错,紧密连续值在第1行第8列报错,两个美化对象则在第4行第1列报错。raw_decode能消费三种序列,而逐行方式只适用于JSON Lines。只有每个值都通过语法与业务检查、尾部完整消费、记录数一致后,才能把已解析对象写到新的临时数组或JSON Lines文件,并保留原文件以便回滚。

原创编辑插画:展示解码字符位置与UTF-8字节偏移的差异。
原创编辑插画:展示解码字符位置与UTF-8字节偏移的差异。 Watch at Ease