Python: 能将解码项目与原始行号关联的JSON解码库?
我经常使用JSON格式来处理用户可以编辑的配置文件。虽然用json.loads
可以帮我检查出格式不正确的JSON,但有时候我在查看解析后的字典、列表或字符串时,才会发现里面有错误。我希望能得到一些有用的错误提示,比如“第23行的值'foo'无效”,但当我拿到字典时,已经失去了和原始行号的对应关系。
听起来可能有人写过一个JSON解析器,可以在输出的每个对象上标记一些关于它在输入文本中位置的元数据:在Python中有没有这样的工具呢?
举个例子:
1. [{"foo": "x"},
2. {"bar": "y"}]
在解析完上面的内容后,我发现“y”其实不是“bar”的合法值,我想知道它是来自第2行的。
3 个回答
如果你使用 json.load()
并且传入一个打开的文件,任何错误信息都会告诉你出错的行号和列号。如果出现的是 ValueError
这种错误,那么相关的提示信息应该适合直接给用户看。
我先说明一下:我是下面这个软件包的维护者。
现在有一个新的Python软件包可以解决这个问题:https://github.com/open-alchemy/json-source-map
安装方法:pip install json_source_map
比如,在你的情况下:
from json_source_map import calculate
source = '''[{"foo": "x"},
{"bar": "y"}]'''
print(calculate(source))
这段代码会输出:
{
"": Entry(
value_start=Location(line=0, column=0, position=0),
value_end=Location(line=1, column=13, position=28),
key_start=None,
key_end=None,
),
"/0": Entry(
value_start=Location(line=0, column=1, position=1),
value_end=Location(line=0, column=13, position=13),
key_start=None,
key_end=None,
),
"/0/foo": Entry(
value_start=Location(line=0, column=9, position=9),
value_end=Location(line=0, column=12, position=12),
key_start=Location(line=0, column=2, position=2),
key_end=Location(line=0, column=7, position=7),
),
"/1": Entry(
value_start=Location(line=1, column=0, position=15),
value_end=Location(line=1, column=12, position=27),
key_start=None,
key_end=None,
),
"/1/bar": Entry(
value_start=Location(line=1, column=8, position=23),
value_end=Location(line=1, column=11, position=26),
key_start=Location(line=1, column=1, position=16),
key_end=Location(line=1, column=6, position=21),
),
}
这个工具会告诉你在原始的JSON文档中,每个键和值的开始和结束位置。举个例子,它会告诉你"y"
在第1行(行数是从0开始算的)并且它从第8列开始,到第11列结束。
据我所知,你想要的功能是不存在的,不过我有个主意,如果你感兴趣的话,可以试试怎么实现...
json模块有一个可以解码对象的钩子,你可以(错误地)利用它来进行解码时的对象验证。不过,这样做并不能解决你的问题,因为这个钩子并不会提供行号信息。问题还更复杂,因为在Python 2.7及以上版本中,你无法逐行获得错误信息。你只能从纯Python的JSON解码器那里得到这些信息,而更新的版本使用的是一个(速度更快的)C语言库。
所以我们需要解决两个问题。
1) 你可以通过继承json.JSONDecoder来使用纯Python的解码器,像这样:
class PyDecoder(json.JSONDecoder):
def __init__(self, encoding=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, strict=True,
object_pairs_hook=None):
super(PyDecoder, self).__init__(encoding, object_hook, parse_float,
parse_int, parse_constant, strict)
self.scan_once = json.scanner.py_make_scanner(self)
2) 为了实现你的验证,你需要用一个方法替换json.decoder.JSONObject,这个方法基本上做同样的事情,但还能把行号信息传递给你的验证程序。