Python 系列(六十一) JSON 是什么?语法规则、数据类型与校验方法
假设你是一名 Linux 运维工程师,需要管理几十台甚至几百台服务器。现在,你想编写一个 Python 程序,记录每台服务器的名称、IP 地址、服务端口和运行状态。
这些信息应该如何保存?如果以后还需要把数据交给其他程序使用,又该采用什么格式?
我们可以使用 JSON。
JSON 是一种非常常见的数据交换格式。在日常开发、Linux 运维、云平台管理以及调用 API 接口时,我们经常会接触 JSON 数据。学习 JSON,不仅能帮助我们理解程序之间如何传递数据,也能为后续使用 Python 的 json 模块打下基础。
什么是 JSON
JSON 是 JavaScript Object Notation 的缩写,中文通常称为 JavaScript 对象表示法。
虽然 JSON 的名字中包含 JavaScript,但它并不局限于 JavaScript。Python、Java、Go、JavaScript 等多种编程语言都可以处理 JSON 数据。
我们可以把 JSON 理解成一种通用的数据记录格式。不同的程序可以按照相同的规则组织和读取数据,从而实现信息交换。
例如,我们想记录一辆汽车的品牌和型号,可以写成下面这样:
1 | { |
这段 JSON 表示一辆汽车:
make:表示汽车品牌,值为GMC。model:表示汽车型号,值为Canyon。
这里的 make 和 model 是键(key),GMC 和 Canyon 是对应的值(value)。
键值对就是把一个名称与一个具体的数据关联起来。例如,服务器名称对应 web01,服务器 IP 地址对应 192.168.1.10。
JSON 使用键值对组织对象中的数据。理解这个概念,是学习 JSON 语法的第一步。
JSON 与 Python 有什么关系
学习 JSON 时,有一个很容易让初学者困惑的问题:JSON 对象和 Python 字典看起来非常相似,它们是不是同一种东西?
答案是:它们不是同一种东西,但可以相互转换。
JSON 是一种数据交换格式,而 Python 字典是 Python 语言提供的一种数据结构。
我们先看看它们各自是什么样子。
JSON 对象:
1 | { |
Python 字典:
1 | server = { |
两段内容看起来几乎一样,但存在一个明显区别:JSON 使用小写的 true,Python 使用大写开头的 True。
如果直接把 JSON 内容复制到 Python 代码中,Python 就可能因为语法错误而无法执行。
除了布尔值,空值等内容也存在类似的区别。因此,我们需要分别理解 JSON 的规则和 Python 的规则。
JSON 数据类型与 Python 数据类型的对应关系
JSON 支持字符串、数字、布尔值、空值、对象和数组。Python 也有对应的数据类型。
| JSON 类型 | JSON 示例 | Python 对应类型 | Python 示例 |
|---|---|---|---|
| 字符串 | "web01" | str | "web01" |
| 整数形式的数字 | 80 | int | 80 |
| 小数形式的数字 | 3.14 | float | 3.14 |
| 布尔值 | true | bool | True |
| 布尔值 | false | bool | False |
| 空值 | null | NoneType | None |
| 对象 | {"name": "web01"} | dict | {"name": "web01"} |
| 数组 | ["nginx", "ssh"] | list | ["nginx", "ssh"] |
这里的 str、int、float、bool、dict 和 list 都是 Python 中的数据类型名称。
如果你暂时还不熟悉这些名称,可以先记住它们的用途:
str:保存文本,例如服务器名称。int:保存整数,例如端口号。float:保存小数,例如 CPU 使用率。bool:表示真或假。dict:保存具有名称的键值对。list:保存一组有顺序的数据。None:表示没有具体的值。
需要注意,JSON 的数字类型并没有严格区分整数和小数,表格中的 int 和 float 是 Python 解析数字时常见的对应结果。
为什么 JSON 和 Python 的写法不完全一样
我们通过几个例子来比较。
布尔值的区别
JSON:
1 | { |
Python:
1 | server = { |
JSON 中的 true 和 false 必须使用小写,而 Python 中的 True 和 False 必须按照 Python 的写法使用大写开头。
空值的区别
JSON:
1 | { |
Python:
1 | server = { |
JSON 使用 null 表示空值,Python 使用 None。
字符串的区别
JSON:
1 | { |
Python:
1 | server = { |
在这个例子中,两者的写法相同。不过,JSON 对象的键必须使用双引号,Python 字典的字符串键则可以使用单引号或双引号。
例如,下面是合法的 Python 字典:
1 | server = { |
但如果把同样的内容直接作为 JSON 使用,就不符合 JSON 的语法要求。
最重要的一点是:不要把 JSON 当成 Python 代码执行,也不要把 Python 字典直接当成 JSON 文本使用。 后续我们会使用 Python 的 json 模块,在两种表示形式之间进行转换。
JSON 的基本语法
了解了 JSON 和 Python 的区别后,我们再来学习 JSON 自身的语法规则。
使用花括号表示对象
JSON 对象使用一对花括号 {} 表示。
例如:
1 | { |
这段 JSON 记录了一台服务器的基本信息。
其中:
"name"是键,对应的值是"web01"。"ip"是键,对应的值是"192.168.1.10"。"status"是键,对应的值是"running"。
每个键和值之间使用冒号 : 分隔,不同的键值对之间使用逗号 , 分隔。
可以把它理解为一张简单的服务器信息表:
| 字段 | 值 |
|---|---|
| name | web01 |
| ip | 192.168.1.10 |
| status | running |
JSON 对象并不是 Excel 表格。它是一种结构化的数据表示方式,程序可以按照约定读取其中的字段和值。
需要注意的是,JSON 对象中的键必须是字符串,并且必须使用双引号包围。
下面是合法的 JSON:
1 | { |
下面的写法则不符合 JSON 语法:
1 | { |
原因有两个:
- JSON 字符串必须使用双引号,不能像 Python 那样使用单引号。
- JSON 不允许在最后一个键值对后面保留多余的逗号。
使用数组保存多个数据
如果我们需要记录多台服务器的信息,就不能只保存一台服务器的数据。
例如,有三台服务器需要管理:
web01:Web 服务器。web02:Web 服务器。db01:数据库服务器。
JSON 可以使用数组保存多个数据。数组使用方括号 [] 表示,数组中的多个元素使用逗号分隔。
例如:
1 | [ |
这个数组包含三个字符串,分别表示三台服务器的名称。
如果希望记录每台服务器的完整信息,也可以将多个对象放入一个数组中:
1 | [ |
这段 JSON 的整体结构是一个数组,数组中的每个元素都是一个对象。
在 Python 中,这种结构通常会被转换为一个列表,列表中的每个元素都是一个字典。
JSON 支持哪些数据类型
JSON 不仅可以保存字符串,还可以保存数字、布尔值、空值、对象和数组。
例如:
1 | { |
这段 JSON 记录了服务器的名称、端口、启用状态、维护状态、描述信息、标签和机房位置。
这里有三个细节需要特别注意。
数字不需要使用双引号。
1 | { |
这里的 80 是数字。如果写成 "80",它就变成了字符串。虽然它们看起来很相似,但程序处理时会将它们视为不同的数据类型。
布尔值必须使用小写。
JSON 使用 true 和 false 表示布尔值,而不是 Python 中的 True 和 False。
空值使用 null。
如果暂时不知道某台服务器的负责人,可以使用 null 表示没有具体值:
1 | { |
JSON 中的 null 与 Python 中的 None 对应,但两者的写法不同。
字符串和特殊字符
JSON 中的字符串必须使用双引号包围。
例如:
1 | { |
如果字符串中包含双引号、换行符等特殊字符,就需要按照 JSON 的规则进行转义。
例如:
1 | { |
其中,反斜杠 \ 用于转义后面的双引号,避免它被误认为字符串的结束位置。
JSON 中常见的转义写法包括:
| 写法 | 含义 |
|---|---|
\" | 双引号 |
\\ | 反斜杠 |
\n | 换行 |
\t | 制表符 |
JSON 不支持注释。不能像 Python 那样直接在 JSON 文件中添加 # 注释,也不能使用 JavaScript 风格的 // 注释。
使用 Python 校验 JSON 语法
前面我们学习了 JSON 的基本规则,但只靠肉眼检查,有时候很难发现错误。
例如,下面的 JSON 看起来没有什么问题:
1 | { |
但实际上,80 后面多了一个逗号,不符合 JSON 语法。
如果配置文件比较长,包含几十甚至几百个字段,逐个检查会非常麻烦。
这时,我们可以使用 Python 自带的 json 模块检查 JSON 是否符合语法规则。
Python 的 json 模块属于标准库,通常不需要额外安装。它可以解析 JSON 文本,也可以把 Python 数据转换成 JSON 文本。
编写第一个 JSON 校验程序
先准备一段合法的 JSON 数据:
1 | { |
现在,我们希望 Python 帮我们检查这段数据能否被正确解析。
在电脑上新建一个名为 check_json.py 的文件,将下面的代码写进去:
1 | import json |
我们逐行理解这段代码。
import json 表示导入 Python 的 json 模块。导入之后,我们就可以使用模块提供的 JSON 处理功能。
data = '''...''' 用于把 JSON 文本保存到 Python 字符串变量 data 中。这里使用三引号,是为了方便在字符串中保留多行内容。三引号属于 Python 语法,不属于 JSON 语法本身。
json.loads(data) 会尝试解析 data 中的 JSON 文本,并将其转换为相应的 Python 数据结构。
例如,这段 JSON 对象会被转换为 Python 字典。
try 和 except 用于处理可能发生的异常。如果 JSON 文本格式错误,Python 就可以捕获 json.JSONDecodeError,而不是直接显示一大段未处理的错误信息。
其中,as e 表示把异常信息保存到变量 e 中,之后可以通过 print(e) 查看具体错误原因。
运行程序并查看输出
打开终端,进入保存 check_json.py 的目录,然后执行:
1 | python check_json.py |
如果你的系统使用 python3 命令,可以执行:
1 | python3 check_json.py |
对于前面给出的合法 JSON,预期输出如下:
1 | JSON 语法正确 |
为什么输出与原来的 JSON 不完全一样?
因为 json.loads() 不只是检查语法,还把 JSON 文本转换成了 Python 数据结构。
转换过程中:
- JSON 对象变成了 Python 字典。
- JSON 的
true变成了 Python 的True。 - 数字
80仍然是整数。 - JSON 字符串仍然是 Python 字符串。
另外,print(result) 显示的是 Python 字典的表示形式,不是原始 JSON 文本。
这说明,JSON 与 Python 数据结构之间存在明确的转换关系,而 json.loads() 正是完成这种转换的函数之一。
故意制造错误,看看 Python 如何处理
理解程序最好的方式之一,就是主动修改代码,观察结果。
现在,我们把 data 中的 JSON 改成下面这样:
1 | import json |
这段代码的问题在于,最后一个键值对后面多了一个逗号。
运行程序:
1 | python check_json.py |
预期输出类似下面这样:
1 | JSON 语法错误 |
具体的行号、列号和字符位置会受到实际文本内容及空白字符的影响,因此不一定与示例完全一致。
这段错误信息的大意是:Python 在这里预期应该出现一个属性名称,也就是 JSON 对象中的下一个键,但实际遇到了不符合语法要求的内容。
因为 JSON 对象中的每个逗号都应该分隔两个键值对。最后一个键值对后面不应该再有逗号,否则解析器会继续期待下一个键。
这时,json.loads() 抛出了 json.JSONDecodeError 异常,程序进入 except 分支,显示错误信息。
通过这个例子,我们可以看到:Python 不仅能够告诉我们 JSON 是否符合语法,还能提供定位错误的线索。
校验真实的 JSON 文件
前面的示例把 JSON 文本直接写在 Python 程序中。实际工作时,我们更经常需要检查独立的配置文件。
例如,我们创建一个名为 servers.json 的文件:
1 | { |
接着,在同一目录下创建 validate_json.py:
1 | import json |
这段程序比前面的示例多了文件读取操作,我们逐步理解。
filename = "servers.json" 表示要检查的文件名。由于这里使用相对路径,程序会从当前工作目录寻找该文件。
open(filename, "r", encoding="utf-8") 用于以文本读取模式打开文件,并使用 UTF-8 编码读取内容。
with 语句会在代码块执行完毕后自动关闭文件,避免文件资源一直处于打开状态。
json.load(f) 与之前的 json.loads(data) 有什么区别?
它们的功能相似,但输入不同:
json.load(f):从文件对象中读取 JSON 并解析。json.loads(data):解析已经保存在字符串中的 JSON 文本。
可以简单记忆:load 主要用于文件对象,loads 主要用于字符串。
这里还增加了一个 FileNotFoundError 异常处理分支。如果文件不存在,程序会显示文件不存在的信息;如果文件存在但 JSON 语法错误,则进入 json.JSONDecodeError 分支。
对于示例中的合法文件,预期输出为:
1 | servers.json 的 JSON 语法正确 |
这里的 len(data['servers']) 用于计算 servers 列表中有多少个元素。因为列表中保存了两台服务器,所以结果是 2。
如果我们故意把 servers.json 的最后一个键值对改成带多余逗号的写法,程序就会报告 JSON 语法错误。
这类校验程序可以用于检查应用配置文件、自动化任务生成的 JSON 数据,以及程序运行前需要读取的配置内容。
需要注意的是,这里检查的是 JSON 语法是否正确。如果文件语法正确,但服务器 IP 地址写错了,或者端口号不符合业务要求,json.load() 仍然可能成功。业务数据是否合理,还需要额外编写检查逻辑。
JSON 在实际工作中有什么用
回到 Linux 运维场景,假设你需要维护一个包含多台服务器的清单。每台服务器都有名称、IP 地址、角色和运行状态。
可以把这些信息保存到 servers.json 文件中:
1 | { |
这种格式具有几个实际优势:
- 结构清晰: 每个字段都有明确的名称,不需要靠记忆判断某个值代表什么。
- 方便维护: 可以按照相同的结构增加、修改或删除服务器信息。
- 便于程序处理: Python 可以读取 JSON 文件,将其中的数据转换成 Python 对象,再执行检查、统计等操作。
- 便于程序之间交换数据: 其他支持 JSON 的程序也可以按照相同的格式读取这些信息。
例如,后续我们可以使用 Python 编写脚本,读取服务器清单,统计运行中的服务器数量,或者找出已经停止的服务器。
在云计算和自动化运维场景中,JSON 也经常用于 API 请求与响应、云资源配置以及应用程序之间的数据交换。
需要注意的是,JSON 本身只负责组织和表示数据,并不会自动连接服务器、检查服务状态或者执行命令。这些操作需要由 Python 程序或其他工具完成。
动手练习
假设你需要记录一台 Linux 服务器的信息,请编写一个 JSON 对象,至少包含以下字段:
hostname:服务器名称,设置为web01。ip:服务器 IP 地址,设置为192.168.1.10。port:服务端口,设置为数字80。enabled:服务是否启用,设置为布尔值true。owner:服务器负责人,暂时没有确定,设置为空值null。services:服务器运行的服务列表,包含nginx和ssh。
完成后,可以对照下面的参考答案检查自己的写法。
1 | { |
练习一:故意写错 JSON,观察校验结果
现在,我们故意在最后一个键值对后面添加一个多余的逗号:
1 | import json |
将代码保存为 check_json.py,然后在终端中运行:
1 | python check_json.py |
如果使用 python3 命令,则运行:
1 | python3 check_json.py |
预期输出类似下面这样:
1 | JSON 语法错误 |
具体的行号、列号和字符位置可能因实际内容中的空白字符而有所不同。
这里出现错误,是因为 JSON 对象的最后一个键值对后面不能有多余的逗号。解析器遇到逗号后,还会期待下一个键,但实际遇到了结束的花括号。
请删除多余的逗号,再运行程序,观察校验结果如何变化。
练习二:区分 JSON 的布尔值和 Python 的布尔值
在 JSON 中,布尔值必须写成小写的 true 和 false。
例如,下面是合法的 JSON:
1 | { |
如果把 true 改成 True:
1 | { |
这段内容就不符合 JSON 语法。
请使用下面的程序验证:
1 | import json |
运行后,程序会输出 JSON 语法错误,并给出错误位置等信息。
这里需要区分两件事:
- 在 JSON 文本中,应该使用
true。 - 在 Python 代码中,布尔值应该使用
True。
例如,下面是合法的 Python 字典:
1 | server = { |
但是,不能因为 Python 使用 True,就把 JSON 中的 true 也改成 True。
练习三:判断语法正确,不代表数据一定正确
再思考一个问题:如果我们把服务器端口写成 -1,JSON 校验会失败吗?
1 | { |
答案是:不会因为端口号为负数而产生 JSON 语法错误。
-1 是合法的 JSON 数字,因此 json.loads() 可以正常解析它。
但是,负数不是有效的 TCP 或 UDP 端口号。也就是说,这份数据虽然符合 JSON 语法,却不符合端口号的业务规则。
通过这个例子,我们可以理解:
- 语法校验:检查数据是否符合 JSON 的格式规则。
- 业务校验:检查数据是否符合实际业务要求。
在编写自动化运维程序时,这两种检查都很重要。
写在最后
JSON 是一种简单、通用的数据交换格式。它通过对象、数组、字符串、数字、布尔值和空值等基本结构,帮助我们清晰地组织数据。
对于 Python 初学者来说,学习 JSON 需要掌握三个重点:
- 理解 JSON 的基本语法,知道对象、数组以及常见数据类型如何表示。
- 理解 JSON 与 Python 数据类型之间的对应关系,特别是
true与True、null与None的区别。 - 学会使用 Python 的
json模块校验 JSON 文本,并理解json.load()和json.loads()的基本用途。
JSON 语法校验只是第一步。接下来,我们还会学习如何读取 JSON 文件、修改数据,以及将 Python 字典和列表转换成 JSON 文本。
下一篇,我们将正式学习 Python 的 json 模块,把这些知识应用到实际程序中。
