python中實現(xiàn)json數(shù)據(jù)和類對象相互轉(zhuǎn)化的四種方式
在日常的軟件測試過程中,測試數(shù)據(jù)的構(gòu)造是一個占比非常大的活動。對于測試數(shù)據(jù)的構(gòu)造,分為結(jié)構(gòu)化的數(shù)據(jù)構(gòu)造方式和非結(jié)構(gòu)化的數(shù)據(jù)構(gòu)造方式,反映python代碼里分別是:
- 定義數(shù)據(jù)的class:修改數(shù)據(jù)中的某一個字段,操作json或者直接針對class的成員變量進(jìn)行修改。
- 不定義數(shù)據(jù)的class: 直接操作json(dict),來構(gòu)造或者修改數(shù)據(jù)。
兩種方式各有優(yōu)缺點, 對于業(yè)務(wù)上有明確業(yè)務(wù)含義的數(shù)據(jù),比如請求數(shù)據(jù),建議使用第一種方式,對數(shù)據(jù)進(jìn)行建模和定義; 而臨時的數(shù)據(jù)構(gòu)造,可以使用第二種方式,不需要額外定義一些結(jié)構(gòu),成本會低一些。在使用于第一種方法時,就會涉及到python中json數(shù)據(jù)與類對象的相互轉(zhuǎn)化的。
此篇文章,會通過4種方式來展示json數(shù)據(jù)與python的類對象相互轉(zhuǎn)化:
python的原生方法
jsonpickle
cattrs和attrs庫
pydantic庫
以下的例子,都使用同一個class,并且使用了嵌套的json,算是一個稍微復(fù)雜的場景。
class Address():
def __init__(self, street, number):
self.street = street
self.number = number
class User():
def __init__(self, name, address):
self.name = name
self.address = Address()
一、python的原生方法
json to python class的參考:https://stackoverflow.com/a/28352366
python class to json的參考: https://stackoverflow.com/a/10252138
以下為代碼示例:
import json
from json import JSONEncoder
class Address():
def __init__(self, street, number):
self.street = street
self.number = number
class User():
def __init__(self, name, address):
self.name = name
self.address = Address(**address)
class MyEncoder(JSONEncoder):
def default(self, o):
return o.__dict__
if __name__ == '__main__':
js = '''{"name":"Cristian", "address":{"street":"Sesame","number":122}}'''
j = json.loads(js)
print(j)
u = User(**j)
print(json.dumps(u, cls = MyEncoder))
print(json.dumps(u.__dict__))
執(zhí)行代碼后,輸出
{'name': 'Cristian', 'address': {'street': 'Sesame', 'number': 122}}
{"name": "Cristian", "address": {"street": "Sesame", "number": 122}}
Traceback (most recent call last):
***********
raise TypeError(f'Object of type {o.__class__.__name__} ‘
TypeError: Object of type Address is not JSON serializable
json轉(zhuǎn)class object時,使用u = User(**j); class object轉(zhuǎn)成json時,對于一級的json,直接使用u.__dict__就可以轉(zhuǎn)成json,而對于嵌套的json,必須使用自定義的JSONEncoder才能轉(zhuǎn)成功。
在以上的轉(zhuǎn)化中,使用了兩個python的特性,簡單解釋一下:
- 雙星號(**) : 在函數(shù)參數(shù)中使用時,用于函數(shù)參數(shù)的解包,使用雙星號(**)來解包一個字典的鍵值對到一個函數(shù)的關(guān)鍵字參數(shù)中。
def greet(name, age):
print(f"Hello, my name is {name} and I am {age} years old.")
person = {'name': 'Alice', 'age': 30}
greet(**person) # 等價于 greet(name='Alice', age=30)
- __dict__屬性: __dict__屬性是一個內(nèi)置屬性,包含了對象的屬性及其值,以字典的形式存儲。上面的例子中,使用__dict__屬性,無法將address的值打印出來,就是因為值為Address對象,而不是一個字符串。
優(yōu)點:
- 不需要引入其他的三方庫
缺點:
一級的json數(shù)據(jù)沒有什么問題,多級的json數(shù)據(jù),to_json轉(zhuǎn)化時,支持的不好,需要自己定義一個JSONEncoder才能轉(zhuǎn)json成功, 如果更復(fù)雜的json,可能會失敗。
二、jsonpickle庫
jsonpickle在github上的介紹如下:
"Python library for serializing any arbitrary object graph into JSON. It can take almost any Python object and turn the object into JSON. Additionally, it can reconstitute the object back into Python.”
這段話,就說明了這是一個專注于class object -> json的庫,而json->object的功能則只支持調(diào)用jsonpickle得到的json,再轉(zhuǎn)回class object.
以下為代碼示例:
import json
import jsonpickle
class Address():
def __init__(self, street, number):
self.street = street
self.number = number
class User():
def __init__(self, name, address):
self.name = name
self.address = Address(**address)
if __name__ == '__main__':
js = '''{"name":"Cristian", "address":{"street":"Sesame","number":122}}'''
j = json.loads(js)
# jsonpickle中沒有一個類似jsonpickle.decode(j, class = User)的方法,所以只能拿第一種方法初始化class
u = User(**j)
print(jsonpickle.encode(u, unpicklable=False))
jp = jsonpickle.encode(u)
print(jp)
u2 = jsonpickle.decode(jp)
print(u2.__class__)
執(zhí)行后的輸出為:
{"name": "Cristian", "address": {"street": "Sesame", "number": 122}}
{"py/object": "__main__.User", "name": "Cristian", "address": {"py/object": "__main__.Address", "street": "Sesame", "number": 122}}
<class '__main__.User'>
一些說明:
1. jsonpickle中沒有一個類似jsonpickle.decode(j, class = User)的方法,所以只能拿第一種方法初始化class
- 轉(zhuǎn)成json時, 調(diào)用jsonpickle.encode方法,傳入?yún)?shù)unpicklable=False時,返回值不包含把json數(shù)據(jù)轉(zhuǎn)回python object的信息,得到一個通用的json字符串
3. 轉(zhuǎn)成json時, 調(diào)用jsonpickle.encode方法,默認(rèn)unpicklable=True時,返回值中包含python object的信息,比如對象的類,輸出中的"py/object": "_main_.User"就是這些信息
- json轉(zhuǎn)成python object時, 必須使用unpicklable=True時的json數(shù)據(jù),jsonpickle在json轉(zhuǎn)object時的局限性就在于此。
優(yōu)點:
- object -> json很強大,可以直接使用
缺點:
- json轉(zhuǎn)object時, 比較雞肋,基本不能直接使用
三、cattrs和attrs庫
attrs庫: https://github.com/python-attrs/attrs。 attrs可以簡化類的定義的管理,使用后這些類將自動獲得一些有用的特性,如初始化方法(_init_)、__repr__方法、__eq__和__hash__等。實際使用的話,最基礎(chǔ)的只需要知道attr.s和attr.ib兩個方法即可。
cattrs庫: https://github.com/python-attrs/cattrs。cattrs(即“conversion attrs”)是一個與attrs緊密集成的庫,它提供了對象到字典(或其他數(shù)據(jù)結(jié)構(gòu))的序列化和從字典(或其他數(shù)據(jù)結(jié)構(gòu))到對象的反序列化功能。
以下為代碼:
import json
import attr
import cattrs
@attr.s
class Address:
street = attr.ib(type = str)
number = attr.ib(type = int)
@attr.s
class User:
name = attr.ib(type = str)
address = attr.ib(type=Address) # adrress 為Addres類型
if __name__ == "__main__":
js = '''{"name":"Cristian", "address":{"street":"Sesame","number":122}}'''
j = json.loads(js)
u = cattrs.structure(j, User)
print(u, u.__class__)
print(cattrs.unstructure(u))
執(zhí)行后,輸出:
User(name='Cristian', address=Address(street='Sesame', number=122), <class '__main__.User'>)
{'name': 'Cristian', 'address': {'street': 'Sesame', 'number': 122}}
一些說明:
- attr.s是一個裝飾器,用來標(biāo)記類為attrs類。
- attr.ib是一個用于聲明屬性的工廠函數(shù), 比如attr.ib(type=str)表示一個類型為str的屬性。在類定義中,使用attr.s和attr.ib基本就夠了,在attr.ib方法中,還有很多參數(shù),比如默認(rèn)值、validator等,可以用于檢查成員是不是滿足定義的屬性。
- cattrs.structure和cattrs.unstructure用來將attrs類和json dict之間的相互轉(zhuǎn)化,含義非常的直觀。
優(yōu)點:
- 使用cattrs和attrs結(jié)合使用,在與json的轉(zhuǎn)化中,非常簡單和強大,而且結(jié)構(gòu)化的數(shù)據(jù)定義非常直觀。
四、pydantic庫
Pydantic庫: https://github.com/pydantic/pydantic。數(shù)據(jù)驗證和解析的Python庫,提供類型注解、數(shù)據(jù)驗證和模型轉(zhuǎn)換功能。使用Pydantic可以定義模型類,驗證輸入數(shù)據(jù)并轉(zhuǎn)換為字典或JSON。
直接上代碼:
import json
from pydantic import BaseModel, Field
class Address(BaseModel):
street: str #pydantic使用類型注解, 來確保使用正確的類型提示來定義字段
number: int
class User(BaseModel):
name: str
address: Address
if __name__ == "__main__":
js = '''{"name":"Cristian", "address":{"street":"Sesame","number":122}}'''
j = json.loads(js)
u = User.parse_obj(j)
print(u, u.__class__)
print(u.json())
執(zhí)行代碼后,輸出:
name='Cristian' address=Address(street='Sesame', number=122) <class '__main__.User'>
{"name": "Cristian", "address": {"street": "Sesame", "number": 122}}
一些說明:
- 通過繼承pydantic的BaseModel來定義類,類型注解來定義字段的類型,創(chuàng)建符合需求的數(shù)據(jù)模型(這里也能看出數(shù)據(jù)模型是pydantic的核心)。
- 繼承BaseModel,直接使用parse_obj方法就可以將json數(shù)據(jù)轉(zhuǎn)為class object, 直接使用json()方法就可以轉(zhuǎn)為json數(shù)據(jù)。
優(yōu)點:
- 在與json的轉(zhuǎn)化中,非常簡單和強大,直接調(diào)用結(jié)構(gòu)體的方法就可,而且結(jié)構(gòu)化的數(shù)據(jù)定義非常直觀
總結(jié):
針對python中類對象和json的相關(guān)轉(zhuǎn)化問題, 本文介紹了4種方式,涉及了三個非常強大的python庫jsonpickle、attrs和cattrs、pydantic,但是這些庫的功能并未涉及太深。在工作中,遇到實際的問題時,可以根據(jù)這幾種方法,靈活選取。
再回到結(jié)構(gòu)化測試數(shù)據(jù)的構(gòu)造,當(dāng)需要對數(shù)據(jù)進(jìn)行建模時,也就是賦予數(shù)據(jù)業(yè)務(wù)含義,pydantic應(yīng)該是首選,目前(2024.7.1)來看,pydantic的生態(tài)非?;钴S,各種基于pydantic的工具也非常多,建議嘗試。
以上就是python中實現(xiàn)json數(shù)據(jù)和類對象相互轉(zhuǎn)化的四種方式的詳細(xì)內(nèi)容,更多關(guān)于python json和類對象相互轉(zhuǎn)化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PyTorch中torch.utils.data.Dataset的介紹與實戰(zhàn)
PyTorch是一個開源的Python機器學(xué)習(xí)庫,基于Torch,用于自然語言處理等應(yīng)用程序,下面這篇文章主要給大家介紹了關(guān)于PyTorch中torch.utils.data.Dataset的介紹與實戰(zhàn),需要的朋友可以參考下2022-06-06
python爬蟲 使用真實瀏覽器打開網(wǎng)頁的兩種方法總結(jié)
下面小編就為大家分享一篇python爬蟲 使用真實瀏覽器打開網(wǎng)頁的兩種方法總結(jié),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-04-04

