如何在Python中解析JSON文件并记录每个解析对象在文件中的行号与列位置
如何在Python中解析JSON文件并记录每个解析对象在文件中的行号与列位置
我完全懂你的需求——想用Python标准库的json模块解析JSON,同时还要能追踪每个解析后元素在原文件里的具体行号和起止列位置,这样当自定义的JSON规则(比如某个字段必须是字符串)被违反时,就能给用户精准的错误提示,而不是泛泛的报错。
可惜默认的json.load()返回的都是普通的字典、列表、字符串这些原生类型,根本不带任何位置信息。不过我们可以通过扩展标准库的JSON解码器,来实现这个功能,完全不需要依赖第三方库。下面是具体的实现步骤:
1. 定义带位置信息的容器类
我们需要自定义几个类来包装解析后的JSON元素,同时存储它们在原字符串中的起始和结束位置(字符偏移量):
import json from json.decoder import JSONDecoder, WHITESPACE class LocationAwareDict(dict): """包装字典,附加整体的起止位置""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.start = None # 元素起始字符偏移 self.end = None # 元素结束字符偏移 class LocationAwareList(list): """包装列表,附加整体的起止位置""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.start = None self.end = None class LocationAwareValue: """包装字符串、数字等基本类型,附加起止位置""" def __init__(self, value, start, end): self.value = value self.start = start self.end = end def __repr__(self): # 保持打印时和原生值一致,方便调试 return repr(self.value)
2. 自定义JSON扫描器,捕获位置信息
标准库的JSON解码器内部用scan_once方法逐个解析元素,我们可以重写这个方法,在解析每个元素时记录它的起止位置,然后用上面的容器类包装:
def decode_with_locations(json_str): decoder = JSONDecoder() # 保存原扫描器方法 original_scan = decoder.scan_once def custom_scan(string, idx): # 先跳过元素前面的空白字符,找到真正的起始位置 idx = WHITESPACE.match(string, idx).end() start_idx = idx # 调用原扫描器解析当前元素 obj, end_idx = original_scan(string, idx) # 根据元素类型包装成带位置的对象 if isinstance(obj, dict): wrapped = LocationAwareDict(obj) wrapped.start = start_idx wrapped.end = end_idx return wrapped, end_idx elif isinstance(obj, list): wrapped = LocationAwareList(obj) wrapped.start = start_idx wrapped.end = end_idx return wrapped, end_idx else: # 基本类型用LocationAwareValue包装 return LocationAwareValue(obj, start_idx, end_idx), end_idx # 替换解码器的扫描器 decoder.scan_once = custom_scan # 解码整个JSON字符串 return decoder.decode(json_str)
3. 字符偏移转成行号和列号
上面的方法得到的是字符偏移量,我们需要把它转换成用户更容易理解的行号和列号(行号、列号都从1开始计数):
def offset_to_line_col(json_str, offset): # 计算offset之前有多少个换行符,行号=换行符数量+1 line_num = json_str.count('\n', 0, offset) + 1 # 找到上一个换行符的位置,计算列号 last_newline_pos = json_str.rfind('\n', 0, offset) if last_newline_pos == -1: # 元素在第一行,列号=偏移量+1 col_num = offset + 1 else: # 列号=当前偏移量 - 上一个换行符的位置 col_num = offset - last_newline_pos return line_num, col_num
4. 测试使用
用你给出的示例JSON文件来测试(假设文件内容是换行后的格式,符合你说的行2的情况):
{ "foo": [ "bar", "baz" ] }
加载并获取元素位置的代码:
with open("example.json", encoding="utf-8") as f: json_content = f.read() # 用自定义解码器解析 parsed_json = decode_with_locations(json_content) # 获取目标元素"bar" bar_element = parsed_json["foo"][0] # 转换位置 start_line, start_col = offset_to_line_col(json_content, bar_element.start) end_line, end_col = offset_to_line_col(json_content, bar_element.end) print(f"元素'bar'的位置:行{start_line},列{start_col}-{end_col}") # 输出:元素'bar'的位置:行2,列11-15
额外说明
- 这个方案完全基于Python标准库,没有任何第三方依赖,符合你的要求。
- 如果需要追踪JSON键的位置(比如"foo"这个键的位置),可以进一步修改
LocationAwareDict,在解析字典时记录每个键的位置,这需要更深入地调整扫描逻辑,但原理是一样的。 - 对于包含转义字符的JSON字符串,只要用UTF-8编码读取,字符偏移量的计算就是准确的。
备注:内容来源于stack exchange,提问作者Philippe Cerfon
相关产品推荐
相关产品推荐

