如何在 Python 中解析(读取)和使用 JSON?
- 2024-11-20 08:44:00
- admin 原创
- 8
问题描述:
我的 Python 程序接收到 JSON 数据,我需要从中获取一些信息。我该如何解析数据并使用结果?我想我需要使用json.loads
这个任务,但我不明白如何做。
例如,假设我有jsonStr = '{"one" : "1", "two" : "2", "three" : "3"}'
。给定这个 JSON 和的输入"two"
,我如何获取相应的数据"2"
?
请注意,这.load
是针对文件的;.loads
是针对字符串的。另请参阅:从文件读取 JSON。
有时,JSON 文档旨在表示表格数据。如果您有类似的东西并尝试将其与 Pandas 一起使用,请参阅Python - 如何将 JSON 文件转换为 Dataframe。
有些数据表面上看起来像 JSON,但实际上不是 JSON。
例如,有时数据来自应用于repr
原生 Python 数据结构。结果可能使用不同的引号,使用标题大小写的True
andFalse
而不是 JSON 强制的true
andfalse
等。对于此类数据,请参阅将字典的字符串表示形式转换为字典或如何将列表的字符串表示形式转换为列表。
另一种常见的变体格式将单独的有效 JSON 格式数据放在输入的每一行上。(正确的 JSON无法逐行解析,因为它使用可能相隔多行的匹配括号。)这种格式称为 JSONL。请参阅将 JSONL 文件加载为 JSON 对象。
有时,来自 Web 源的 JSON 数据会填充一些额外的文本。在某些情况下,这可以绕过浏览器中的安全限制。这称为 JSONP,在什么是 JSONP,为什么创建它?中有描述。在其他情况下,额外的文本实现了一种安全措施,如Google 为什么在其 JSON 响应前面添加 while(1);所述。无论哪种方式,在 Python 中处理这个问题都很简单:只需识别并删除多余的文本,然后像以前一样继续。
解决方案 1:
非常简单:
import json
data = json.loads('{"one" : "1", "two" : "2", "three" : "3"}')
print(data['two']) # or `print data['two']` in Python 2
解决方案 2:
有时你的 JSON 不是字符串。例如,如果你从这样的 URL 获取 JSON:
j = urllib2.urlopen('http://example.com/data.json')
您将需要使用json.load()
,而不是json.loads()
:
j_obj = json.load(j)
(很容易忘记:'s' 代表 'string')
解决方案 3:
对于 URL 或文件,使用json.load()
。对于包含 .json 内容的字符串,使用json.loads()
。
#! /usr/bin/python
import json
# from pprint import pprint
json_file = 'my_cube.json'
cube = '1'
with open(json_file) as json_data:
data = json.load(json_data)
# pprint(data)
print "Dimension: ", data['cubes'][cube]['dim']
print "Measures: ", data['cubes'][cube]['meas']
解决方案 4:
以下是可能对您有帮助的简单示例:
json_string = """
{
"pk": 1,
"fa": "cc.ee",
"fb": {
"fc": "",
"fd_id": "12345"
}
}"""
import json
data = json.loads(json_string)
if data["fa"] == "cc.ee":
data["fb"]["new_key"] = "cc.ee was present!"
print json.dumps(data)
上述代码的输出将是:
{"pk": 1, "fb": {"new_key": "cc.ee was present!", "fd_id": "12345",
"fc": ""}, "fa": "cc.ee"}
请注意,您可以设置 dump 的 ident 参数来像这样打印它(例如,当使用 print json.dumps(data, indent=4) 时):
{
"pk": 1,
"fb": {
"new_key": "cc.ee was present!",
"fd_id": "12345",
"fc": ""
},
"fa": "cc.ee"
}
解决方案 5:
解析数据
使用标准库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
很容易记住区别:后面s
的loads
代表“字符串”。(诚然,这可能不符合标准的现代命名惯例。)
请注意,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)
使用requests
或其他隐含支持
当使用流行的第三方requests
库从互联网检索数据时,无需从对象中提取.text
(或创建任何类型的文件类对象)Response
并单独解析它。相反,Response
对象直接提供了一个.json
执行此解析的方法:
import requests
response = requests.get('https://www.example.com')
parsed = response.json()
该方法接受与标准库功能相同的关键字参数json
。
使用结果
通过上述任何一种方法进行解析,默认情况下都会产生一个完全普通的Python 数据结构,该数据结构由完全普通的内置类型 dict
、list
、str
、、(JSON和成为 Python 常量和)和(JSON成为 Python 常量)组成。int
`floatbool
truefalse
TrueFalse
NoneTypenull
None`
因此,使用该结果的方法与使用任何其他技术获取相同数据的方法相同。
因此,继续问题中的例子:
>>> parsed
{'one': '1', 'two': '2', 'three': '3'}
>>> parsed['two']
'2'
我之所以强调这一点,是因为很多人似乎期望结果会有些特别,但事实并非如此。它只是一个嵌套的数据结构,尽管处理嵌套有时很难理解。
例如,考虑像这样的解析结果result = {'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
dict
,但 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 格式使用的转义序列。
在上面的例子中,我使用input
解释器提示符来显示示例数据,以避免与转义混淆。这是一个在源代码中使用字符串文字的类似示例:
>>> json.loads('{"one": "{\\\"two\\\": \\\"three\\\", \\\"backslash\\\": \\\"\\\\\\\\\\\"}"}')
{'one': '{"two": "three", "backslash": "\\\\\"}'}
要使用双引号字符串文字,字符串文字中的双引号也需要转义。因此:
>>> json.loads('{\"one\": \"{\\\"two\\\": \\\"three\\\", \\\"backslash\\\": \\\"\\\\\\\\\\\"}\"}')
{'one': '{"two": "three", "backslash": "\\\\\"}'}
\"
输入中的每个序列都会变成"
实际的 JSON 数据,"
在 JSON 解析器解析时会变成(嵌入字符串中)。类似地,实际 JSON 数据中的\\\\\"
(五对反斜杠,然后是一个转义的引号)会变成\\"
(五个反斜杠和一个引号;等价地,两对反斜杠,然后是一个转义的引号),\"
在 JSON 解析器解析时会变成\\"
(两个反斜杠和一个引号),在解析结果的字符串表示中会变成(两个转义的反斜杠和一个引号)(因为现在引号不需要转义,因为 Python 可以使用单引号作为字符串;但反斜杠仍然需要)。
简单定制
除了strict
选项之外,可用的关键字选项json.load
应该json.loads
是回调。解析器将调用它们,传入部分数据,并使用返回的内容来创建整体结果。
“解析”钩子相当不言自明。例如,我们可以指定将浮点值转换为decimal.Decimal
实例,而不是使用原生 Python float
:
>>> import decimal
>>> json.loads('123.4', parse_float=decimal.Decimal)
Decimal('123.4')
或者对每个值使用浮点数,即使它们可以转换为整数:
>>> 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 dict
。 提供object_pairs_hook
将使用一个参数调用,该参数是用于 的键值对列表dict
。 它应该返回所需的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
将使用原本要创建的来调用provided 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
使用。
文本编码问题和bytes
/或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
值来表示原始数据(在 2.x 中str
是的别名bytes
),并接受值repr
会有unicode
前缀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 版本相对应的全面文档。
解决方案 6:
pathlib.Path
对象是处理文件路径的安全、高效且灵活的方法。
因此,这里是使用 pathlib.Path 对象从文件读取 json 的另一种解决方案:
import json
from pathlib import Path
# Create the path object that points to the file which contains json formatted data
json_file_path = Path("mydata.json")
# One-liner for reading the data and converting it to a `dict`
data = json.loads(json_file_path.read_text())
print(data["two"])
免责声明:当使用对象时,此问题及其从文件 重复中读取的内容没有一行解决方案
pathlib.Path
。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件