python 如何解析(读取)和使用JSON?

fhity93d  于 2023-01-19  发布在  Python
关注(0)|答案(5)|浏览(162)

我的Python程序接收JSON数据,我需要从中获取一些信息。我如何解析数据并使用结果呢?我认为我需要使用json.loads来完成这项任务,但我不知道如何执行。
例如,假设我有jsonStr = '{"one" : "1", "two" : "2", "three" : "3"}',给定这个JSON,输入"two",我怎样才能得到相应的数据"2"
有时候,JSON文档是用来表示表格数据的,如果你有类似的东西,并试图在Pandas中使用它,请参阅Python - How to convert JSON File to Dataframe
有些数据表面上看起来像JSON,但不是JSON
例如,有时数据来自于将repr应用到原生Python数据结构,结果可能使用不同的引号,使用标题大小写的TrueFalse,而不是JSON强制的truefalse,等等。
另一种常见的变体格式是将单独的有效JSON格式的数据放在输入的每一行上。(正确的JSON无法逐行解析,因为它使用了可以分隔许多行的对称括号。)这种格式称为JSONL。请参见Loading JSONL file as JSON objects
有时来自web源的JSON数据会被填充一些额外的文本。在某些上下文中,这可以绕过浏览器中的安全限制。这被称为JSONP,在What is JSONP, and why was it created?中有描述。在其他上下文中,额外的文本实现了一种安全措施,如Why does Google prepend while(1); to their JSON responses?中所描述。无论哪种方式,在Python中处理这一点都很简单:只需识别并删除多余的文本,然后像以前一样继续。

0yycz8jy

0yycz8jy1#

很简单:

import json
data = json.loads('{"one" : "1", "two" : "2", "three" : "3"}')
print(data['two'])  # or `print data['two']` in Python 2
pftdvrlh

pftdvrlh2#

有时你的json不是字符串,例如,如果你从一个url中获取一个json,如下所示:

j = urllib2.urlopen('http://site.com/data.json')

你需要使用json.load,而不是json.loads:

j_obj = json.load(j)

(it很容易被遗忘:"s“代表”字符串“)

u7up0aaq

u7up0aaq3#

对于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']
kcwpcxri

kcwpcxri4#

下面是一个简单的例子,可能会对您有所帮助:

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"
}
fkvaft9z

fkvaft9z5#

解析数据

使用标准库json模块

对于字符串数据,使用json.loads

import json

text = '{"one" : "1", "two" : "2", "three" : "3"}'
parsed = json.loads(example)

对于来自文件或其他file-like object的数据,请使用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'

这两个函数都提供了相同的附加选项集来定制解析过程。从Python 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库从Internet检索数据时,没有必要Response对象中提取.text(或创建任何类型的类似文件的对象)并单独解析它。相反,Response对象直接提供了一个.json方法来执行此解析:

import requests
response = requests.get('https://www.example.com')
parsed = response.json()

此方法接受与标准库json功能相同的关键字参数。

使用结果

默认情况下,使用上述任何一种方法进行解析都会得到一个 * 非常普通 * 的Python数据结构,由 * 非常普通的内置类型 * dictliststrintfloat组成。bool(JSON truefalse变为Python常量TrueFalse)和NoneType(JSON null变为Python常量None)。
因此,处理这个结果的工作方式 * 与 * 使用任何其他技术 * 获得 * 相同数据 * 的方式 * 相同。
因此,继续这个问题的例子:

>>> parsed
{'one': '1', 'two': '2', 'three': '3'}
>>> parsed['two']
'2'

我强调这一点是因为许多人似乎期望结果有一些特别之处;它只是一个嵌套的数据结构,处理嵌套有时候很难理解。
例如,考虑像result = {'a': [{'b': 'c'}, {'d': 'e'}]}这样的解析结果,要获得'e',需要一次执行一个适当的步骤:在Dict中查找a密钥给出列表[{'b': 'c'}, {'d': 'e'}];列表第二元素(索引1)是{'d': 'e'};然后在其中查找'd'键,得到'e'值。因此,对应的代码为result['a'][1]['d']:按顺序应用每个索引步骤。
有时候,人们希望应用更复杂的选择标准,迭代嵌套列表,过滤或转换数据,等等。这些都是将在其他地方处理的更复杂的主题。

常见的困惑来源

JSON相似

在尝试解析JSON数据之前,确保数据实际上是JSON是很重要的。检查JSON format specification以验证预期的内容。

  • 文档表示一个值(通常是JSON“object”,对应于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解析器解析时,它变成\\"(两个反斜杠和一个引号),在解析结果的字符串表示中,它变成\\\\"(两个转义反斜杠和一个引号)(从现在开始,引号不需要转义,因为Python可以对字符串使用单引号;但反斜杠仍然起作用**)。

简单的定制

除了strict选项之外,json.loadjson.loads的关键字选项应该是callbacks,解析器将调用它们,传入部分数据,并使用返回的任何内容来创建整个结果。
“parse”钩子是非常简单明了的,例如,我们可以指定将浮点值转换为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_hookobject_pairs_hook的自定义示例

object_hookobject_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将改为使用否则将创建的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基本上是一种文本格式。在解析文件之前,应该使用适当的编码将输入数据从原始字节转换为文本。
在Python 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的默认值,虽然original specification, ECMA-404不要求编码(它只描述“JSON文本”,而不是JSON文件或文档),但RFC 8259要求:
在不属于封闭生态系统的系统之间交换的JSON文本必须使用UTF-8编码[RFC 3629]。
在这样的“封闭生态系统”中(即对于编码不同且不会公开共享的本地文档),首先明确应用适当的编码:

>>> 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本身has not been maintained since Jan 1, 2020)应始终使用unicode值表示文本,使用str值表示原始数据(strbytes在2.x中的别名),并接受unicode值的repr将具有u前缀(毕竟,代码应该关心的是值实际上是什么,而不是它在REPL中的样子)。

历史记录:simplejson

simplejsonis simply the standard library json module,而不是maintained and developed externally。它最初是在JSON支持添加到Python标准库之前创建的。在Python 2.6中,simplejson项目作为json合并到标准库中。当前的开发维护了与Python 2.5的兼容性,尽管还有一个未维护的遗留分支应该支持最早的Python 2.2。
标准库通常使用相当旧的包版本;例如,我的3.8.10安装报告

>>> json.__version__
'2.0.9'

而最新的发行版(截至本文撰写之时)是3.18.1(Github库中标记的发行版最早只能追溯到3.8.2;2.0.9版本的发布日期为to 2009
到目前为止,我还无法找到哪些simplejson版本对应于哪些Python发行版的全面文档。

相关问题