解析数据
使用标准库json模块
对于字符串数据,使用json.loads:
import json
text = '{"one" : "1", "two" : "2", "three" : "3"}'
parsed = json.loads(example)
对于来自文件或其他类文件对象的数据,使用json.load:
import io, json
# create an in-memory file-like object for demonstration purposes.
text = '{"one" : "1", "two" : "2", "three" : "3"}'
stream = io.StringIO(text)
parsed = json.load(stream) # load, not loads
很容易记住它们的区别:loads后面的s代表“string”。(不可否认,这可能不符合标准的现代命名惯例。)
注意json。Load不接受文件路径:
>>> json.load('example.txt')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.8/json/__init__.py", line 293, in load
return loads(fp.read(),
AttributeError: 'str' object has no attribute 'read'
这两个函数为定制解析过程提供了相同的附加选项集。从3.6开始,选项仅限关键字。
对于字符串数据,也可以使用标准库提供的JSONDecoder类,如下所示:
import json
text = '{"one" : "1", "two" : "2", "three" : "3"}'
decoder = json.JSONDecoder()
parsed = decoder.decode(text)
同样的关键字参数是可用的,但是现在它们被传递给JSONDecoder的构造函数,而不是.decode方法。该类的主要优点是它还提供了一个.raw_decode方法,该方法将忽略JSON结束后的额外数据:
import json
text_with_junk = '{"one" : "1", "two" : "2", "three" : "3"} ignore this'
decoder = json.JSONDecoder()
# `amount` will count how many characters were parsed.
parsed, amount = decoder.raw_decode(text_with_junk)
使用请求或其他隐式支持
当使用流行的第三方请求库从Internet检索数据时,不需要从Response对象中提取.text(或创建任何类型的类文件对象)并单独解析它。相反,Response对象直接提供了一个.json方法来进行解析:
import requests
response = requests.get('https://www.example.com')
parsed = response.json()
该方法接受与标准库json功能相同的关键字参数。
使用结果
默认情况下,通过上述任何方法进行解析都会得到一个非常普通的Python数据结构,由非常普通的内置类型dict、list、str、int、float、bool (JSON true和false成为Python常量true和false)和NoneType (JSON null成为Python常量None)组成。
因此,处理这一结果就像使用其他技术获得相同的数据一样。
因此,我们继续这个问题的例子:
>>> parsed
{'one': '1', 'two': '2', 'three': '3'}
>>> parsed['two']
'2'
我之所以强调这一点,是因为许多人似乎认为这个结果有一些特别之处;没有。它只是一个嵌套的数据结构,尽管处理嵌套有时很难理解。
考虑,例如,解析结果像结果= {a: [{' b ': ' c '}, {' d ': ' e '}]}。要获得'e',需要一次遵循适当的步骤:在字典中查找a键会给出一个列表[{'b': 'c'}, {'d': 'e'}];该列表的第二个元素(索引1)是{'d': 'e'};在这里查找'd'键得到'e'值。因此,相应的代码是result['a'][1]['d']:按顺序应用每个索引步骤。
请参见如何从嵌套数据结构中提取单个值(例如从解析JSON中)?
有时人们想要应用更复杂的选择标准,迭代嵌套列表,过滤或转换数据等。这些都是更复杂的话题,将在其他地方讨论。
常见的困惑来源
JSON疯
在尝试解析JSON数据之前,重要的是要确保数据实际上是JSON。检查JSON格式规范以验证预期的结果。重点:
文档表示一个值(通常是一个JSON“对象”,它对应于Python字典,但JSON表示的任何其他类型都是允许的)。特别是,它在每行上没有单独的条目——这就是JSONL。
使用标准文本编码(通常为UTF-8)后,数据是人类可读的。几乎所有的文本都包含在双引号中,并在适当的地方使用转义序列。
处理嵌入式数据
考虑一个包含以下内容的示例文件:
{"one": "{\"two\": \"three\", \"backslash\": \"\\\\\"}"}
这里的反斜杠用于JSON的转义机制。
当使用上述方法之一进行解析时,我们会得到如下结果:
>>> example = input()
{"one": "{\"two\": \"three\", \"backslash\": \"\\\\\"}"}
>>> parsed = json.loads(example)
>>> parsed
{'one': '{"two": "three", "backslash": "\\\\"}'}
注意parsed['one']是一个str,而不是dict。然而,碰巧的是,该字符串本身表示“嵌入的”JSON数据。
要用已解析的结果替换嵌入的数据,只需访问数据,使用相同的解析技术,然后从那里开始(例如,在适当的地方更新原始结果):
>>> parsed['one'] = json.loads(parsed['one'])
>>> parsed
{'one': {'two': 'three', 'backslash': '\\'}}
注意,这里的'\\'部分表示的是包含一个实际反斜杠的字符串,而不是两个。这遵循了通常的Python字符串转义规则,这将我们带到……
JSON转义与Python字符串文字转义
有时,人们在尝试测试涉及解析JSON的代码时感到困惑,并在Python源代码中以不正确的字符串文字提供输入。在尝试测试需要使用嵌入式JSON的代码时尤其会发生这种情况。
问题是JSON格式和字符串文本格式各自有各自的转义数据策略。Python将在字符串文本中处理转义,以便创建字符串,然后仍然需要包含JSON格式使用的转义序列。
在上面的示例中,我在解释器提示符处使用输入来显示示例数据,以避免与转义混淆。下面是一个在源代码中使用字符串文字的类似示例:
>>> json.loads('{"one": "{\\"two\\": \\"three\\", \\"backslash\\": \\"\\\\\\\\\\"}"}')
{'one': '{"two": "three", "backslash": "\\\\"}'}
要使用双引号字符串文字,字符串文字中的双引号也需要转义。因此:
>>> json.loads('{\"one\": \"{\\\"two\\\": \\\"three\\\", \\\"backslash\\\": \\\"\\\\\\\\\\\"}\"}')
{'one': '{"two": "three", "backslash": "\\\\"}'}
Each sequence of \\\" in the input becomes \" in the actual JSON data, which becomes " (embedded within a string) when parsed by the JSON parser. Similarly, \\\\\\\\\\\" (five pairs of backslashes, then an escaped quote) becomes \\\\\" (five backslashes and a quote; equivalently, two pairs of backslashes, then an escaped quote) in the actual JSON data, which becomes \\" (two backslashes and a quote) when parsed by the JSON parser, which becomes \\\\" (two escaped backslashes and a quote) in the string representation of the parsed result (since now, the quote does not need escaping, as Python can use single quotes for the string; but the backslashes still do).
简单的定制
除了strict选项之外,json还提供了关键字选项。加载和json。负载应该是回调的。解析器将调用它们,传入部分数据,并使用返回的数据创建整体结果。
“解析”钩子是不言自明的。例如,我们可以指定将浮点值转换为小数。十进制实例,而不是使用本机Python浮点数:
>>> import decimal
>>> json.loads('123.4', parse_float=decimal.Decimal)
Decimal('123.4')
或者对每个值都使用float,即使它们可以转换为整数:
>>> json.loads('123', parse_int=float)
123.0
或拒绝转换JSON的特殊浮点值表示:
>>> def reject_special_floats(value):
... raise ValueError
...
>>> json.loads('Infinity')
inf
>>> json.loads('Infinity', parse_constant=reject_special_floats)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.8/json/__init__.py", line 370, in loads
return cls(**kw).decode(s)
File "/usr/lib/python3.8/json/decoder.py", line 337, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
File "/usr/lib/python3.8/json/decoder.py", line 353, in raw_decode
obj, end = self.scan_once(s, idx)
File "<stdin>", line 2, in reject_special_floats
ValueError
使用object_hook和object_pairs_hook的定制示例
object_hook和object_pairs_hook可用于控制在给定JSON对象时解析器的操作,而不是创建Python字典。
所提供的object_pairs_hook将使用一个参数调用,该参数是一个键值对列表,否则将用于字典。它应该返回所需的dict或其他结果:
>>> def process_object_pairs(items):
... return {k: f'processed {v}' for k, v in items}
...
>>> json.loads('{"one": 1, "two": 2}', object_pairs_hook=process_object_pairs)
{'one': 'processed 1', 'two': 'processed 2'}
一个被提供的object_hook将会被调用,否则将会创建dict,结果将会替换为:
>>> def make_items_list(obj):
... return list(obj.items())
...
>>> json.loads('{"one": 1, "two": 2}', object_hook=make_items_list)
[('one', 1), ('two', 2)]
如果两者都提供,则将忽略object_hook,只使用object_items_hook。
文本编码问题和字节/unicode混淆
JSON基本上是一种文本格式。在解析文件之前,应该首先使用适当的编码将输入数据从原始字节转换为文本。
在3。x,支持从bytes对象加载,并隐式使用UTF-8编码:
>>> json.loads('"text"')
'text'
>>> json.loads(b'"text"')
'text'
>>> json.loads('"\xff"') # Unicode code point 255
'ÿ'
>>> json.loads(b'"\xff"') # Not valid UTF-8 encoded data!
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.8/json/__init__.py", line 343, in loads
s = s.decode(detect_encoding(s), 'surrogatepass')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 1: invalid start byte
UTF-8通常被认为是JSON的默认值。虽然最初的规范ECMA-404没有强制要求编码(它只描述了“JSON文本”,而不是JSON文件或文档),RFC 8259要求:
在不属于封闭生态系统的系统之间交换的JSON文本必须使用UTF-8 [RFC3629]编码。
在这样的“封闭生态系统”中(即对于编码不同且不会公开共享的本地文档),首先显式地应用适当的编码:
>>> json.loads(b'"\xff"'.decode('iso-8859-1'))
'ÿ'
类似地,JSON文件应该以文本模式打开,而不是二进制模式。如果文件使用不同的编码,在打开它时只需指定:
with open('example.json', encoding='iso-8859-1') as f:
print(json.load(f))
在2。x,字符串和字节序列没有正确区分,这导致了许多问题和混乱,特别是在使用JSON时。
2.积极维护;X代码库(请注意2。x本身自2020年1月1日以来一直没有维护)应该始终使用unicode值来表示文本,str值来表示原始数据(str是2.x中字节的别名),并接受unicode值的repr将有一个u前缀(毕竟,代码应该关心的是值实际是什么,而不是它在REPL中看起来是什么)。
历史注释:simplejson
Simplejson只是标准库json模块,但是由外部维护和开发。它最初是在JSON支持添加到Python标准库之前创建的。在2.6中,simplejson项目作为json被合并到标准库中。目前的开发将兼容性保持到2.5,尽管还有一个未维护的遗留分支应该支持早至2.2的版本。
标准库通常使用相当旧的包版本;例如,我的3.8.10安装报告
>>> json.__version__
'2.0.9'
而最新的版本(在撰写本文时)是3.18.1。(Github存储库中带标签的版本只能追溯到3.8.2;2.0.9版本发布于2009年。
到目前为止,我还无法找到关于哪个simplejson版本对应哪个Python版本的全面文档。