在Python中使用JSON

在Python中使用JSON

在Python中使用JSON

原文連結: [Working With JSON Data in Python](https://realpython。com/python-json/)

JSON簡史

最初,JSON只是JavaScript中用於處理物件的文字語法。只不過到如今,JSON已成為語言無關語言以來就很久了,並且作為其自己的標準存在,因此,為了便於討論,我們可以避免使用JavaScript。

以下是一段簡短的JSON:

{

“firstName”: “Jane”,

“lastName”: “Doe”,

“hobbies”: [“running”, “sky diving”, “singing”],

“age”: 35,

“children”: [

{

“firstName”: “Alice”,

“age”: 6

},

{

“firstName”: “Bob”,

“age”: 8

}

}

如您所見,JSON支援基本型別,例如字串和數字,以及巢狀列表和物件。

Python原生支援JSON

Python帶有一個內建程式包json用於編碼和解碼JSON資料:

import json

編碼JSON的過程通常稱為

序列化

。該術語指的是將資料轉換為一系列位元組(因此稱為serial)以在網路上儲存或傳輸。自然,

反序列化

是對已經以JSON標準儲存或傳遞的資料進行解碼的對等過程。

序列化JSON

計算機處理大量資訊後會發生什麼?它需要進行資料轉儲。因此,json庫dump()函式是用於將資料寫入檔案的方法(而dumps()是用於寫入字串的方法)。

根據相當直觀的轉換,將簡單的Python物件轉換為JSON。

在Python中使用JSON

一個簡單的序列化示例

想象一下,您正在使用記憶體中的Python物件,看起來有點像這樣:

data = {

“president”: {

“name”: “Zaphod Beeblebrox”,

“species”: “Betelgeusian”

}

}

將這些資訊儲存到磁碟至關重要,因此您的任務是將其寫入檔案。

使用Python的上下文管理器,您可以建立一個名為的檔案data_file。json並以寫入模式開啟它:

with open(“data_file。json”, “w”) as write_file:

json。dump(data, write_file)

dump()帶有兩個位置引數:

要序列化的資料物件

將要寫入的類檔案位元組物件(file-like object)

或者,如果您傾向於繼續在程式中使用此序列化的JSON資料,則可以將其寫入本地Python str物件:

json_string = json。dumps(data)

請注意,由於您實際上並未將資料寫入磁碟,因此不存在類檔案位元組物件(file-like object)。除此之外,dumps()與dump()功能類似。

一些有用的關鍵字引數

請記住,JSON程式碼格式易於人類閱讀,但是如果將所有語法壓縮在一起,那麼語法可讀性就不那麼明顯。如果,您擁有與我不同的程式設計風格,並且按自己的喜好格式化程式碼後,閱讀起來可能會更容易。

您可以使用indent關鍵字引數來指定巢狀結構的縮排大小。透過使用data上面定義的,並在控制檯中執行以下命令,檢視異同:

json。dumps(data)

json。dumps(data, indent=4)

另一個格式化選項是 separators關鍵字引數。預設情況下,這是字串的2個分隔符字元(“, ”, “: ”),但是緊湊型JSON的常見替代方法是(“,”, “:”)。再次檢視示例JSON,看看這些分隔符在哪裡起作用。

反序列化JSON

在json庫中,您將找到load()和loads(),它們是將JSON編碼的資料轉換為Python物件的方法。

與序列化一樣,有一個簡單的反序列化轉換表,儘管您可能已經猜到了它的樣子。

在Python中使用JSON

從技術上講,這種轉換並不是對序列表的完美逆轉。這基本上意味著,如果您現在編碼一個物件,然後在以後再次對其進行解碼,則可能無法獲得完全相同的物件。

實際上,這更像是讓一個朋友將某物翻譯成日語,而另一個朋友將其翻譯成英語。無論如何,最簡單的示例是,對一個tuple進行編碼,然後在解碼後則返回的是list ,如下所示:

>>> blackjack_hand = (8, “Q”)

>>> encoded_hand = json。dumps(blackjack_hand)

>>> decoded_hand = json。loads(encoded_hand)

>>> blackjack_hand == decoded_hand

False

>>> type(blackjack_hand)

>>> type(decoded_hand)

>>> blackjack_hand == tuple(decoded_hand)

True

一個簡單的反序列化示例

這次,想象一下您已經在磁碟上儲存了一些要在記憶體中處理的資料。您仍將使用上下文管理器,但是這次您將以data_file。json讀取模式開啟現有的管理器:

with open(“data_file。json”, “r”) as read_file:

data = json。load(read_file)

此方法的結果可能會從轉換表中返回任何允許的資料型別,在大多數情況下,根物件將是dict或list。

如果您已經從另一個程式中提取了JSON資料,或者以其他方式在Python中獲得了JSON格式的資料字串,則可以輕鬆地使用loads()來反序列化它:

json_string = “”“

{

”researcher“: {

”name“: ”Ford Prefect“,

”species“: ”Betelgeusian“,

”relatives“: [

{

”name“: ”Zaphod Beeblebrox“,

”species“: ”Betelgeusian“

}

}

}

”“”

data = json。loads(json_string)

例項

首先建立一個名為scratch。py或任何您想要的指令碼檔案。

您需要向JSONPlaceholder服務發出API請求,因此只需使用requests包即可完成繁重的工作。在檔案頂部新增以下匯入:

import json

import requests

向JSONPlaceholder服務發起請求:

response = requests。get(“https://jsonplaceholder。typicode。com/todos”)

todos = json。loads(response。text)

現在讓我們來檢查一下todos的型別:

>>> todos == response。json()

True

>>> type(todos)

>>> todos[:10]

。。。

您可以透過在瀏覽器中訪問(https://jsonplaceholder。typicode。com/todos) 來檢視資料的結構,透過觀察,您將看到TODO的示例如下:

{

“userId”: 1,

“id”: 1,

“title”: “delectus aut autem”,

“completed”: false

}

有多個使用者,每個使用者都有一個唯一的userId,每個任務都有一個completed屬性。

下面,我們透過程式設計來確定哪些使用者完成了最多的任務嗎:

# Map of userId to number of complete TODOs for that user

todos_by_user = {}

# Increment complete TODOs count for each user。

for todo in todos:

if todo[“completed”]:

try:

# Increment the existing user‘s count。

todos_by_user[todo[“userId”]] += 1

except KeyError:

# This user has not been seen。 Set their count to 1。

todos_by_user[todo[“userId”]] = 1

# Create a sorted list of (userId, num_complete) pairs。

top_users = sorted(todos_by_user。items(),

key=lambda x: x[1], reverse=True)

# Get the maximum number of complete TODOs。

max_complete = top_users[0][1]

# Create a list of all users who have completed

# the maximum number of TODOs。

users = []

for user, num_complete in top_users:

if num_complete < max_complete:

break

users。append(str(user))

max_users = “ and ”。join(users)

編碼和解碼自定義Python物件

當我們嘗試序列化Elf會發生什麼?

class Elf:

def __init__(self, level, ability_scores=None):

self。level = level

self。ability_scores = {

“str”: 11, “dex”: 12, “con”: 10,

“int”: 16, “wis”: 14, “cha”: 13

} if ability_scores is None else ability_scores

self。hp = 10 + self。ability_scores[“con”]

結果是無法序列化:

>>> elf = Elf(level=4)

>>> json。dumps(elf)

TypeError: Object of type ’Elf‘ is not JSON serializable

儘管該json模組可以處理大多數內建的Python型別,但是對於自定義資料型別,則無法自動序列化。

簡化資料結構

現在,問題是如何處理更復雜的資料結構。好吧,您可以嘗試手動編碼和解碼JSON,但是有一個稍微聰明一點的解決方案可以為您節省一些工作。您可以直接執行一箇中間步驟,而不是從自定義資料型別直接轉換為JSON。

您需要做的就是用 json 內建型別表示資料。本質上,您將較複雜的物件轉換為更簡單的表示形式,然後 json 模組將其轉換為JSON。就像數學中的傳遞性一樣:如果A = B並且B = C,則A =C。

Python有一個內建型別複數(complex),它是用於表示複數的資料型別,並且預設情況下無法序列化:

>>> z = 3 + 8j

>>> type(z)

>>> json。dumps(z)

TypeError: Object of type ’complex‘ is not JSON serializable

使用自定義型別時要問自己一個很好的問題是,重新建立此物件所需的最少資訊量是多少?對於複數而言,您只需要知道實部和虛部:

>>> z。real

3。0

>>> z。imag

8。0

將相同的數字傳遞到一個複數(complex)建構函式中,使用__eq__比較運算子檢查結果:

>>> complex(3, 8) == z

True

將自定義資料型別分解為基本元件對於序列化和反序列化過程都至關重要。

編碼自定義型別

要將自定義物件轉換為JSON,您所需要做的就是為該dump()方法的default引數提供編碼功能。json模組將在本身不支援序列化的任何物件上呼叫這個函式。以下是解碼函式:

def encode_complex(z):

if isinstance(z, complex):

return (z。real, z。imag)

else:

type_name = z。__class__。__name__

raise TypeError(f“Object of type ’{type_name}‘ is not JSON serializable”)

請注意,如果您沒有得到所期望的物件則會丟擲TypeError錯誤。現在,您可以使用以下程式碼進行序列號複數:

>>> json。dumps(9 + 5j, default=encode_complex)

’[9。0, 5。0]‘

>>> json。dumps(elf, default=encode_complex)

TypeError: Object of type ’Elf‘ is not JSON serializable

為什麼我們將複數編碼為

tuple

?

好問題!那當然不是唯一的選擇,也不一定是最好的選擇。您可以根據自己的需求,任意切換,只不過tuple是更常用的選擇。

另一種常見方法是繼承JSONEncoder並覆蓋其default()方法:

class ComplexEncoder(json。JSONEncoder):

def default(self, z):

if isinstance(z, complex):

return (z。real, z。imag)

else:

return super()。default(z)

您可以透過cls引數直接在dump()方法中使用ComplexEncoder,也可以透過建立編碼器的例項並呼叫其encode()方法來使用:

>>> json。dumps(2 + 5j, cls=ComplexEncoder)

’[2。0, 5。0]‘

>>> encoder = ComplexEncoder()

>>> encoder。encode(3 + 6j)

’[3。0, 6。0]‘

解碼自定義型別

儘管複數的實部和虛部絕對必要,但實際上它們不足以重新建立物件。當您嘗試使用ComplexEncoder編碼一個複數,然後解碼時,會發生以下情況:

>>> complex_json = json。dumps(4 + 17j, cls=ComplexEncoder)

>>> json。loads(complex_json)

[4。0, 17。0]

您得到的只是一個列表,如果您再次想要該複數物件,則必須將值傳遞給建構函式。

那麼,我們可以在complex_data。json新增複數標識,如下:

{

“__complex__”: true,

“real”: 42,

“imag”: 36

}

然後改寫decode_complex():

def decode_complex(dct):

if “__complex__” in dct:

return complex(dct[“real”], dct[“imag”])

return dct

如果“__complex__”不在字典中,則可以返回該物件,並讓預設解碼器處理它。

現在,您可以從完成對complex_data。json的資料解析:

>>> with open(“complex_data。json”) as complex_data:

。。。 data = complex_data。read()

。。。 z = json。loads(data, object_hook=decode_complex)

。。。

>>> type(z)

嘗試將以下複數列表放入complex_data。json並再次執行指令碼:

{

“__complex__”:true,

“real”:42,

“imag”:36

},

{

“__complex__”:true,

“real”:64,

“imag”:11

}

如果一切順利,您將獲得complex物件列表:

>>> with open(“complex_data。json”) as complex_data:

。。。 data = complex_data。read()

。。。 numbers = json。loads(data, object_hook=decode_complex)

。。。

>>> numbers

[(42+36j), (64+11j)]

結論

恭喜,您現在可以在您的Python程式碼中加入JSON強大的功能。

我們來複習下JSON常規任務的工作流程:

匯入 json 包

使用 load() 或者 loads()讀取資料

處理資料

使用dump() 或者 dumps()寫入更改後的資料