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)文章
Python使用pptx實現(xiàn)復(fù)制頁面到其他PPT中
這篇文章主要為大家詳細(xì)介紹了python如何使用pptx庫實現(xiàn)從一個ppt復(fù)制頁面到另一個ppt里面,文中的示例代碼講解詳細(xì),感興趣的可以嘗試一下2023-02-02python將紅底證件照轉(zhuǎn)成藍(lán)底的實現(xiàn)方法
這篇文章主要介紹了python將紅底證件照轉(zhuǎn)成藍(lán)底,本文給大家分享四種方法通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08Python使用grequests(gevent+requests)并發(fā)發(fā)送請求過程解析
這篇文章主要介紹了Python使用grequests并發(fā)發(fā)送請求過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-09-09Python try except異常捕獲機(jī)制原理解析
這篇文章主要介紹了Python try except異常捕獲機(jī)制原理解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-04-04