JSON文件存储
JSON,全称为 JavaScript Object Notation,也就是 JavaScript 对象标记,通过对象和数组的组合来表示数据,虽构造简洁但是结构化程度非常高,是一种轻量级的数据交换格式。
本节我们就来了解如何利用 Python 将数据存储为 JSON 文件。
对象和数组
在 JavaScript 语言中,一切皆为对象,因此任何支持的数据类型都可以通过 JSON 表示,例如字符串、数字、对象、数组等。其中对象和数组是比较特殊且常用的两种类型,下面简要介绍一下这两者。
对象在 JavaScript 中是指用花括号 {} 包围起来的内容,数据结构是 {key1: value1, key2: value2,…} 这种键值对结构。在面向对象的语言中,key表示对象的属性、value 表示属性对应的值,前者可以使用整数和字符串表示,后者可以是任意类型。
数组在 JavaScript 中是指用方括号[]包围起来的内容,数据结构是["jaγa","javascript", "vb", …] 这种索引结构。在 JavaScript 中,数组是一种比较特殊的数据类型,因为它也可以像对象那样使用键值对结构,但还是索引结构用得更多。同样,它的值可以是任意类型。
所以,一个 JSON 对象可以写为如下形式:
[{
"name": "Bob",
"gender": "male",
"birthday": "1992-10-18"
},{
"name": "Selina",
"gender": "female",
"birthday": "1995-10-18"
}]
由 [] 包围的内容相当于数组,数组中的每个元素都可以是任意类型,这个实例中的元素是对象,由 {} 包围。
JSON 可以由以上两种形式自由组合而成,能够嵌套无限次,并且结构清晰,是数据交换的极佳实现方式。
读取 JSON
Python 为我们提供了简单易用的 JSON 库,用来实现 JSON 文件的读写操作,我们可以调用 JSON 库中的 loads 方法将 JSON 文本字符串转为 JSON 对象。实际上,JSON 对象就是 Python 中列表和字典的嵌套与组合。反过来,我们可以通过 dumps 方法将 JSON 对象转为文本字符串。
例如,这里有一段 JSON 形式的字符串,是 str 类型,我们用 Python 将其转换为可操作的数据结构,如列表或字典:
import json
str = '''
[{
"name": "Bob",
"gender": "male",
"birthday": "1992-10-18"
},{
"name": "Selina",
"gender": "female",
"birthday": "1995-10-18"
}]
'''
print(type(str))
data = json.loads(str)
print(data)
print(type(data))
运行结果如下:
<class 'str'>
[{'name': 'Bob', 'gender': 'male', 'birthday': '1992-10-18'}, {'name': 'Selina', 'gender': 'female', 'birthday': '1995-10-18'}]
<class 'list'>
这里使用 loads 方法将字符串转为了 JSON 对象。由于最外层是中括号,所以最终的数据类型是列表类型。
这样一来,我们就可以用索引获取对应的内容了。例如,要想获取第一个元素里的 name 属性,可以使用如下方式:
data[0]['name']
data[0].get('name')
得到的结果都是:
Bob
以中括号加 0 作为索引,可以得到第一个字典元素,再调用其键名即可得到相应的键值。获取键值的方式有两种,一种是中括号加键名,另一种是利用 get 方法传人键名。这里推荐使用 get 方法。这样即使键名不存在,也不会报错,而是会返回 None。另外,get 方法还可以传人第二个参数(即默认值),实例如下:
data[0].get('age')
data[0].get('age', 25)
运行结果如下:
None
25
这里我们尝试获取年龄 age,原字典中并不存在该键名,因此会默认返回 None。此时如果传入了第二个参数,就会返回传入的这个值。
值得注意的是,JSON 的数据需要用双引号包围起来,而不能使用单引号。例如使用如下形式,就会出现错误:
import json
str = '''
[{
'name': 'Bob',
'gender': 'male',
'birthday': '1992-10-18'
}]
'''
data = json.loads(str)
运行结果如下:
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 3 column 5 (char 8)
这里出现了 JSON 解析错误的提示,其原因就是数据由单引号包围着。再次强调,请千万注意 JSON 字符串的表示需要用双引号,否则 loads 方法会解析失败。
下面实现从 JSON 文本中读取内容,例如有一个 data.json 文本文件,其内容是刚才定义的 JSON 字符串,我们可以先将文本文件中的内容读出,再利用 loads 方法将之转化为 JSON 对象:
import json
with open('data.json', encoding='utf-8') as file:
str = file.read()
data = json.loads(str)
print(data)
运行结果如下:
这里我们使用 open 方法读取文本文件,使用的是默认的读模式,编码指定为 utf-8,并文件操作对象赋值为 file。然后我们调用 file 对象的 read 方法读取了文本中的所有内容,赋值为 str。接着再调用 loads 方法解析 JSON 字符串,将其转化为 JSON 对象。
其实上述实例有更简便的写法,可以直接使用 load 方法传人文件操作对象,同样也可以将文本转化为 JSON 对象,写法如下:
import json
data = json.load(open('data.json', encoding='utf-8'))
print(data)
注意这里使用的是 load 方法,而不是 loads 方法。前者的参数是一个文件操作对象,后者的参数是一个 JSON 字符串。
这两种写法的运行结果是完全一样的。只不过 load 方法是将整个文件中的内容转化为 JSON 对象,而 loads 方法可以更灵活地控制要转换那些内容。两种方法可以在适当的场景下选择使用。
输出 JOSN
可以调用 dumps 方法将 JSON 对象转化为字符串。例如,将上面例子的运行结果中的列表重新写入文本:
import json
data = [{
'name': 'Bob',
'gender': 'male',
'birthday': '1992-10-18'
}]
with open('data.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(data))
这里利用 dumps 方法,将 JSON 对象转为了字符串,然后调用文件的 write 方法将字符串写入文本,结果如图 4-2 所示。
另外,如果想保存 JSON 对象的缩进格式,可以再往 dumps 方法中添加—个参数 indent,代表缩进字符的个数。实例如下:
with open('data.json', 'w') as file:
file.write(json.dumps(data, indent=2))
此时写入结果如图 4-3 所示。
能够看出,得到的内容自带缩进,格式更加清晰。
另外,如果 JSON 对象中包含中文字符,会怎么样呢?现在将之前 JSON 对象中的部分值改为中文,并且依然用之前的方法将之写人文本:
import json
data = [{
'name': '王伟',
'gender': '男',
'birthday': '1992-10-18'
}]
with open('data.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(data, indent=2))
写人结果如图 4-4 所示。
可以看到,文本中的中文字符都变成了 Unicode 字符,这显然不是我们想要的结果。
要想输出中文,还需要指定参数 ensure_ascii 为 False,以及规定文件输出的编码:
with open('data.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(data, indent=2, ensure_ascii=False))
此时的写人结果如图 4-5 所示。
能够发现,现在可以将 JSON 对象输出为中文了。
类比 loads 与 load 方法,dumps 同样也有对应的 dump 方法,它可以直接将 JSON 对象全部写入文件中,因此上述写法也可以写为如下形式:
json.dump(data, open('data.json', 'w', encoding='utf-8'), indent=2, ensure_ascii=False)
这里第一个参数是 JSON 对象,第二个参数可以传入文件操作对象,其他的 indent、ensure_ascii 对象还是保持不变,运行结果是一样的。