使用 Python 处理 JSON 数据

623 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

一、引言:什么是 JSON

JSON (Java Script Object Notation) 是一种很常用的数据格式,它常常用在 web 应用程序中。它可以表示结构化的数据。

下面是常见的 JSON 文件结构

 {
     "name": "Kamishiro Rize",
     "age": "22",
     "occupation": "firefighter",
     "traits": [
         "Eagle Eyed",
         "Fast Healer",
         "High Thirst",
         "Hearty Appetite"
     ]
 }

它看起来与 Python 的 字典非常类似,也是由 key - value 结对组成,其中key是字符串形式,value是字符串、数字、布尔值、数组、对象或null。key/value间均使用逗号进行区分。

在 Python 中,JSON 作为字符串存在

 json_str = '{"name": "Kamishiro Rize", "age": "22", "profession": "firefighter", "traits": ["Eagle Eyed", "Fast Healer", "High Thirst", "Hearty Appetite"]}'

JSON 与 Python 的数据结构和对应关系如下:

JSONPYTHON
objectdict
arraylist, tuple
stringstr, unicode
numberint, long, float
true / falseTrue / False
nullNone

要使用 JSON ,字符串或者包含 JSON 对象的文件,都可以使用 Python 的内置包 json 模块。

 import json

二、示例:在 Python 中解析 JSON

JSON 模组的常用方法

load / loads: 把 JSON 转换为 Python

  • loads()
 # some json
 somebody_info = '{"name": "Wenjie Ye", "age": 75, "nationality": "China"}'
 ​
 # parse to dict
 j = json.loads(somebody_info)
 ​
 # show result
 print(j["name"])
 print(j["age"])
 print(type(j))

结果

 Wenjie Ye
 75
 <class 'dict'>

将 JSON 转换为 Python 后,其结果的类型为字典

  • load()
 # some json
 somebody_info = '{"name": "Wenjie Ye", "age": 75, "nationality": "China"}'
 ​
 # use json.load
 # j = json.load(somebody_info)  # AttributeError: 'str' object has no attribute 'read'
 ​
 from io import StringIO
 io = StringIO(somebody_info)
 j = json.load(io)
 print(type(j))
 print(j)

load() 是从json格式的文件中读取数据并转换为python的类型。适用于文件读取,所以我们按 loads() 的例子来操作是会出错的,可以使用 StringIO 转换一下。load() 的结果也是返回字典

 <class 'dict'>
 {'name': 'Wenjie Ye', 'age': 75, 'nationality': 'China'}

dump / dumps: 把 Python 转换为 JSON

  • dumps()
 python_dict = {
     'name': 'Wenjie Ye',
     'age': 75,
     'nationality': 'China',
 }
 ​
 # convert to JSON
 j = json.dumps(python_dict)
 ​
 # result
 print(j)
 print(type(j))

转换后的结果返回字符串

 {"name": "Wenjie Ye", "age": 75, "nationality": "China"}
 <class 'str'>
  • dump()

有了 load() 的经验,你应该知道,不带 s 的 dump 方法是用来将python数据类型转换并保存到json格式的文件内的。

 from io import StringIO
 ​
 io = StringIO()
 json.dump('{"name": "Wenjie Ye", "age": 75, "nationality": "China"}', io)
 ​
 content = io.getvalue()
 print(content)

结果

 "{"name": "Wenjie Ye", "age": 75, "nationality": "China"}"

总结

  • dumps / dump: 将 Python 转换 JSON,返回的 type 为 str
  • loads / load: 将 JSON 转换为 Python,返回的 type 为 Dict
  • 如果要根据字符串转化方法中使用带有 s 的,要从文件进行转化就不加 s

优雅的使用 json 模块

格式化 JSON 结果

不难发现,dumps 获得的 str 结果并不是很好看,如果数据量大,或者数据结构复杂,没有缩进和换行将使得 JSON 数据变得不容易阅读。

所以 dumps() 方法提供了一些令结果更易读的参数,这些参数在实际工作中也常常用到。

  • indent 参数:定义缩进数
 python_dict = {
     'name': 'Wenjie Ye',
     'age': 75,
     'nationality': 'China',
     'occupations': ['Astrophysicist', 'University Professor'],
 }
 ​
 res = json.dumps(python_dict, indent=4)
 print(res)

转换的结果将按照 indent 缩进 4 格

 {
     "name": "Wenjie Ye",
     "age": 75,
     "nationality": "China",
     "occupations": [
         "Astrophysicist",
         "University Professor"
     ]
 }
  • separators 参数:更改默认分隔符

我们先来看看官方对其的定义:

If specified, separators should be an (item_separator, key_separator) tuple. The default is (', ', ': ') if indent is None and (',', ': ') otherwise. To get the most compact JSON representation, you should specify (',', ':') to eliminate whitespace.

  1. 类型应该传入元组
  2. 其默认值是 (',', ': ')

元组的第一个分隔符为 key-value 之间的分隔,默认是逗号;第二个分隔符为 key 与 value 之间的分隔,默认是冒号。

我们可以更改分隔符的样式:

 res = json.dumps(python_dict, indent=4, separators=(". ", " = "))
 print(res)

结果

 {
     "name" = "Wenjie Ye". 
     "age" = 75. 
     "nationality" = "China". 
     "occupations" = [
         "Astrophysicist". 
         "University Professor"
     ]
 }
  • sort_keys 参数: 对结果排序,布尔值
 res = json.dumps(python_dict, indent=4, sort_keys=True)
 print(res)

结果

 {
     "age": 75,
     "name": "Wenjie Ye",
     "nationality": "China",
     "occupations": [
         "Astrophysicist",
         "University Professor"
     ]
 }

json 模块不支持转换 bytes 类型

需要注意的是对于 bytes,json 模块并不能顺利转换,要先将bytes转换为str格式

 b = b"bytes content"
 # j = json.dumps(b)  # TypeError: Object of type bytes is not JSON serializable
 ​
 j = json.dumps(b.decode())
 print(j)  # "bytes content"

直接转换 bytes 的结果是 TypeError,会告知你 bytes 不可JSON序列化, 只有转换为 str 类型后才可以序列化。

json 文件读写

 import json
 python_dict = {"k1": "v1", "k2": 123, "k3": ["I'm", "NutCat"]}
 ​
 # write
 f_json = json.dump(python_dict, open("E:\temp\temp.json", "w"))
 print(f_json)  # return None
 ​
 # read
 import os
 os.chdir("E:\temp\")
 # check temp.json exist
 print(os.listdir())
 # read json file
 print(json.load(open("E:\temp\temp.json")))

结果

 None
 ['temp.json']
 {'k1': 'v1', 'k2': 123, 'k3': ["I'm", 'NutCat']}

当然,我还是推荐使用 with open 的方式来写入数据

 with open("E:\temp\temp.json", "w") as f:
     json.dump(python_dict, f)

利用 pandas 读取 JSON

 import pandas as pd
 ​
 df = pd.read_json("E:\temp\temp.json")
 print(df.head())

如果你想利用 DataFrame 的特性来处理数据,你还可以使用 Pandas 库来读取数据,它读取我们之前生成的 temp.json 的结果如下:

    k1   k2      k3
 0  v1  123     I'm
 1  v1  123  NutCat

毫无疑问,我们可以用上强大的 pandas 的特性来处理 json 数据了。

但是,实际工作中,json 文件的内容可不像我们 temp.json 文件一样简单到朴实无华,我们需要知道怎么处理嵌套的 JSON 数据

有如下的 JSON 数据,保存在 json_test.json 文件中,members 字段中保存有 object 类型的数据,这些嵌套的数据在读取到 DataFrame 后会被转换为字典。

 {
     "system_id": 707077,
     "system_name": "account_system",
     "formed": 2022,
     "update_time": "2022-06-06",
     "members": [
         {
             "username": "Kamishiro Rize",
             "age": "22",
             "account": "12345678",
             "nationality": "Japan",
             "active": false
         },
         {
             "username": "Wenjie Ye",
             "age": "75",
             "account": "87654321",
             "nationality": "China",
             "active": true
         }
     ]
 }

现在,我们按照以前的方法读取它

 import os
 import pandas as pd
 ​
 df = pd.read_json("json_test.json")
 print(df)

读取的结果如下

    system_id     system_name  formed update_time  \
 0     707077  account_system    2022  2022-06-06   
 1     707077  account_system    2022  2022-06-06   
 ​
                                              members  
 0  {'username': 'Kamishiro Rize', 'age': '22', 'a...  
 1  {'username': 'Wenjie Ye', 'age': '75', 'accoun...  

其中的 members 字段是保存了一整个字典的,那么应该如何把他拆分开呢?其实,这一步已经和 json 无关了,是依靠 pandas 来处理这些嵌套的数据了。

我们可以在 members 列上,使用 apply 方法

 df["members"].apply(pd.Series)

返回了 DataFrame 结果

     username    age account nationality active
 0   Kamishiro Rize  22  12345678    Japan   False
 1   Wenjie Ye   75  87654321    China   True

但是,使用 apply 方法后生成了一个新 DataFrame,那我们还得想个办法给拼回去原来的 DataFrame。

其实,pandas 库中还有一个函数 json_normalize()

 import json
 import pandas as pd
 with open("json_test.json") as f:
     acct_info = json.load(f)
 res = pd.json_normalize(
     acct_info,
     record_path=["members"],
     meta=["system_id", "system_name", "formed", "update_time"],
 )
 print(res)

它会将 members 拆分并拼接到 DataFrame 结果中

          username age   account nationality  active system_id     system_name  \
 0  Kamishiro Rize  22  12345678       Japan   False    707077  account_system   
 1       Wenjie Ye  75  87654321       China    True    707077  account_system   
 
   formed update_time  
 0   2022  2022-06-06  
 1   2022  2022-06-06  
  • record_path: 需要拆分的列的名字
  • meta: 其他要加入到结果的列名的list,其顺序就是输出的顺序
  • meta_prefix: 这个参数可以给 meta 的字段名前加个前缀

\