python中json和类对象的相互转化

简介: 针对python中类对象和json的相关转化问题, 本文介绍了4种方式,涉及了三个非常强大的python库jsonpickle、attrs和cattrs、pydantic,但是这些库的功能并未涉及太深。在工作中,遇到实际的问题时,可以根据这几种方法,灵活选取。再回到结构化测试数据的构造,当需要对数据进行建模时,也就是赋予数据业务含义,pydantic应该是首选,目前(2024.7.1)来看,pydantic的生态非常活跃,各种基于pydantic的工具也非常多,建议尝试。

在日常的软件测试过程中,测试数据的构造是一个占比非常大的活动。对于测试数据的构造,分为结构化的数据构造方式和非结构化的数据构造方式,反映python代码里分别是:

  1. 定义数据的class:修改数据中的某一个字段,操作json或者直接针对class的成员变量进行修改。
  2. 不定义数据的class: 直接操作json(dict),来构造或者修改数据。


两种方式各有优缺点, 对于业务上有明确业务含义的数据,比如请求数据,建议使用第一种方式,对数据进行建模和定义; 而临时的数据构造,可以使用第二种方式,不需要额外定义一些结构,成本会低一些。在使用于第一种方法时,就会涉及到python中json数据与类对象的相互转化的。


此篇文章,会通过4种方式来展示json数据与python的类对象相互转化

  1. python的原生方法
  2. jsonpickle
  3. cattrs和attrs库
  4. pydantic库


以下的例子,都使用同一个class,并且使用了嵌套的json,算是一个稍微复杂的场景。

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的原生方法


以下为代码示例:

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__))

执行代码后,输出

{'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转class object时,使用u = User(**j); class object转成json时,对于一级的json,直接使用u.__dict__就可以转成json,而对于嵌套的json,必须使用自定义的JSONEncoder才能转成功。


在以上的转化中,使用了两个python的特性,简单解释一下:


  • 双星号(**) : 在函数参数中使用时,用于函数参数的解包,使用双星号(**)来解包一个字典的键值对到一个函数的关键字参数中。
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__属性是一个内置属性,包含了对象的属性及其值,以字典的形式存储。上面的例子中,使用__dict__属性,无法将address的值打印出来,就是因为值为Address对象,而不是一个字符串。


优点:

  • 不需要引入其他的三方库


缺点:

  • 一级的json数据没有什么问题,多级的json数据,to_json转化时,支持的不好,需要自己定义一个JSONEncoder才能转json成功, 如果更复杂的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的功能则只支持调用jsonpickle得到的json,再转回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__)

执行后的输出为:

{"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
  2. 转成json时, 调用jsonpickle.encode方法,传入参数unpicklable=False时,返回值不包含把json数据转回python object的信息,得到一个通用的json字符串
  3. 转成json时, 调用jsonpickle.encode方法,默认unpicklable=True时,返回值中包含python object的信息,比如对象的类,输出中的"py/object": "_main_.User"就是这些信息
  4. json转成python object时, 必须使用unpicklable=True时的json数据,jsonpickle在json转object时的局限性就在于此。


优点:

  • object -> json很强大,可以直接使用

缺点:

  • json转object时, 比较鸡肋,基本不能直接使用

三、 cattrs和attrs库

attrs库: github.com/python-attr…。 attrs可以简化类的定义的管理,使用后这些类将自动获得一些有用的特性,如初始化方法(_init_)、__repr__方法、__eq__和__hash__等。实际使用的话,最基础的只需要知道attr.s和attr.ib两个方法即可。


cattrs库: github.com/python-attr…。cattrs(即“conversion attrs”)是一个与attrs紧密集成的库,它提供了对象到字典(或其他数据结构)的序列化和从字典(或其他数据结构)到对象的反序列化功能。


以下为代码:

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))

执行后,输出:

User(name='Cristian', address=Address(street='Sesame', number=122), <class '__main__.User’>)
{'name': 'Cristian', 'address': {'street': 'Sesame', 'number': 122}}

一些说明:

  1. attr.s是一个装饰器,用来标记类为attrs类。
  2. attr.ib是一个用于声明属性的工厂函数, 比如attr.ib(type=str)表示一个类型为str的属性。在类定义中,使用attr.s和attr.ib基本就够了,在attr.ib方法中,还有很多参数,比如默认值、validator等,可以用于检查成员是不是满足定义的属性。
  3. cattrs.structure和cattrs.unstructure用来将attrs类和json dict之间的相互转化,含义非常的直观。

优点:

  • 使用cattrs和attrs结合使用,在与json的转化中,非常简单和强大,而且结构化的数据定义非常直观。

四、pydantic库

Pydantic库: github.com/pydantic/py…。数据验证和解析的Python库,提供类型注解、数据验证和模型转换功能。使用Pydantic可以定义模型类,验证输入数据并转换为字典或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())

执行代码后,输出:

name='Cristian' address=Address(street='Sesame', number=122) <class '__main__.User'>
{"name": "Cristian", "address": {"street": "Sesame", "number": 122}}

一些说明:

  • 通过继承pydantic的BaseModel来定义类,类型注解来定义字段的类型,创建符合需求的数据模型(这里也能看出数据模型是pydantic的核心)。
  • 继承BaseModel,直接使用parse_obj方法就可以将json数据转为class object, 直接使用json()方法就可以转为json数据。


优点:

  • 在与json的转化中,非常简单和强大,直接调用结构体的方法就可,而且结构化的数据定义非常直观

总结:

针对python中类对象和json的相关转化问题, 本文介绍了4种方式,涉及了三个非常强大的python库jsonpickle、attrs和cattrs、pydantic,但是这些库的功能并未涉及太深。在工作中,遇到实际的问题时,可以根据这几种方法,灵活选取。


再回到结构化测试数据的构造,当需要对数据进行建模时,也就是赋予数据业务含义,pydantic应该是首选,目前(2024.7.1)来看,pydantic的生态非常活跃,各种基于pydantic的工具也非常多,建议尝试。

相关文章
|
29天前
|
数据采集 JSON 数据处理
抓取和分析JSON数据:使用Python构建数据处理管道
在大数据时代,电商网站如亚马逊、京东等成为数据采集的重要来源。本文介绍如何使用Python结合代理IP、多线程等技术,高效、隐秘地抓取并处理电商网站的JSON数据。通过爬虫代理服务,模拟真实用户行为,提升抓取效率和稳定性。示例代码展示了如何抓取亚马逊商品信息并进行解析。
抓取和分析JSON数据:使用Python构建数据处理管道
|
14天前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
1月前
|
索引 Python
python-类属性操作
【10月更文挑战第11天】 python类属性操作列举
20 1
|
1月前
|
Java C++ Python
Python基础---类
【10月更文挑战第10天】Python类的定义
24 2
|
1月前
|
JSON 数据格式 Python
Python实用记录(十四):python统计某个单词在TXT/JSON文件中出现的次数
这篇文章介绍了一个Python脚本,用于统计TXT或JSON文件中特定单词的出现次数。它包含两个函数,分别处理文本和JSON文件,并通过命令行参数接收文件路径、目标单词和文件格式。文章还提供了代码逻辑的解释和示例用法。
44 0
Python实用记录(十四):python统计某个单词在TXT/JSON文件中出现的次数
|
1月前
|
JSON 数据格式
用来返回Json数据格式的工具--通用类
用来返回Json数据格式的工具--通用类
19 1
WK
|
1月前
|
Python
Python类命名
在Python编程中,类命名至关重要,影响代码的可读性和维护性。建议使用大写驼峰命名法(如Employee),确保名称简洁且具描述性,避免使用内置类型名及单字母或数字开头,遵循PEP 8风格指南,保持项目内命名风格一致。
WK
14 0
|
1月前
|
程序员 开发者 Python
深度解析Python中的元编程:从装饰器到自定义类创建工具
【10月更文挑战第5天】在现代软件开发中,元编程是一种高级技术,它允许程序员编写能够生成或修改其他程序的代码。这使得开发者可以更灵活地控制和扩展他们的应用逻辑。Python作为一种动态类型语言,提供了丰富的元编程特性,如装饰器、元类以及动态函数和类的创建等。本文将深入探讨这些特性,并通过具体的代码示例来展示如何有效地利用它们。
38 0
|
1月前
|
Python
Python中的类(一)
Python中的类(一)
|
1月前
|
Python
Python中的类(一)
Python中的类(一)
下一篇
无影云桌面