我使用以下代码来解析 .json 文件中存储的流中以逗号分隔的 JSON 多行对象:
def stream_read_json(fn):
import json
start_pos = 0
with open(fn, 'r', encoding='utf-8') as f:
while True:
try:
obj = json.load(f)
yield obj
return
except json.JSONDecodeError as e:
f.seek(start_pos)
json_str = f.read(e.pos)
obj = json.loads(json_str, encoding = 'utf-8')
start_pos += e.pos
yield obj
第一个对象已正确解析;接下来的,不是。
在测试
f.seek(start_pos)
的随机值时,我发现与
except json.JSONDecodeError as e:
找到的索引不一致,为什么该索引与我在IDE上选择文本直到JSON对象所在的字符时显示的字符数不同在文件上结束?
如何确保正确解析对象?
尝试在调试提示符处获取
f.seek(start_pos)
第二个 JSON 对象。
代码存在几个问题,导致在尝试解析文件中的多个 JSON 对象时出现“字符索引不一致”错误。让我们来分析一下问题,并提供解决方案:
问题分析
-
f.read(e.pos)
不保证读取完整的 JSON 对象。json.JSONDecodeError
中的e.pos
指示发生错误的字符位置,但不一定是完整 JSON 对象的结束位置。这可能导致读取不完整的 JSON 字符串,从而在后续解析中出现错误。 -
start_pos += e.pos
的逻辑错误。 即使e.pos
正确指向了完整 JSON 对象的末尾,start_pos
的更新方式也会跳过对象之间的分隔符(例如逗号或换行符),导致后续解析从错误的位置开始。
解决方案
为了解决这些问题,我们需要确保每次读取完整的 JSON 对象,并正确更新
start_pos
。以下是一种改进后的实现:
import json
def stream_read_json(fn):
with open(fn, 'r', encoding='utf-8') as f:
buffer = ''
for line in f:
buffer += line
while buffer:
try:
obj, index = json.JSONDecoder().raw_decode(buffer)
yield obj
buffer = buffer[index:].lstrip() # 去除前导空格
except json.JSONDecodeError:
break
代码解释
-
按行读取文件: 使用
for line in f
逐行读取文件内容。 -
使用缓冲区: 将读取的行添加到
buffer
中,直到成功解析出一个完整的 JSON 对象。 -
json.JSONDecoder().raw_decode(buffer)
: 使用raw_decode
方法解析 JSON 对象。该方法返回解析后的对象和下一个 JSON 对象的起始索引。 -
更新缓冲区: 使用解析后的索引
index
更新buffer
,确保下次解析从下一个 JSON 对象开始。 -
处理错误: 如果
json.JSONDecodeError
被引发,说明当前缓冲区中没有完整的 JSON 对象。此时,继续读取下一行并添加到缓冲区中。
示例
假设
data.json
文件包含以下内容:
{"name": "Alice", "age": 30}
{"name": "Bob", "age": 25}
使用改进后的
stream_read_json
函数:
for obj in stream_read_json('data.json'):
print(obj)
输出:
{'name': 'Alice', 'age': 30}
{'name': 'Bob', 'age': 25}
总结
通过按行读取、使用缓冲区和
raw_decode
方法,我们可以更可靠地解析包含多个 JSON 对象的文件,避免“字符索引不一致”的错误。