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的工具也非常多,建议尝试。

相关文章
|
3月前
|
JSON API 数据格式
Python采集京东商品评论API接口示例,json数据返回
下面是一个使用Python采集京东商品评论的完整示例,包括API请求、JSON数据解析
|
3月前
|
存储 JSON API
Python与JSON:结构化数据的存储艺术
Python字典与JSON格式结合,为数据持久化提供了便捷方式。通过json模块,可轻松实现数据序列化与反序列化,支持跨平台数据交换。适用于配置管理、API通信等场景,兼具可读性与高效性,是Python开发中不可或缺的数据处理工具。
132 0
|
12天前
|
JSON API 数据安全/隐私保护
Python采集淘宝评论API接口及JSON数据返回全流程指南
Python采集淘宝评论API接口及JSON数据返回全流程指南
|
12天前
|
安全 大数据 程序员
Python operator模块的methodcaller:一行代码搞定对象方法调用的黑科技
`operator.methodcaller`是Python中处理对象方法调用的高效工具,替代冗长Lambda,提升代码可读性与性能。适用于数据过滤、排序、转换等场景,支持参数传递与链式调用,是函数式编程的隐藏利器。
53 4
|
3天前
|
缓存 供应链 芯片
电子元件类商品 item_get - 商品详情接口深度分析及 Python 实现
电子元件商品接口需精准返回型号参数、规格属性、认证及库存等专业数据,支持供应链管理与采购决策。本文详解其接口特性、数据结构与Python实现方案。
|
2月前
|
JSON 安全 API
Python处理JSON数据的最佳实践:从基础到进阶的实用指南
JSON作为数据交换通用格式,广泛应用于Web开发与API交互。本文详解Python处理JSON的10个关键实践,涵盖序列化、复杂结构处理、性能优化与安全编程,助开发者高效应对各类JSON数据挑战。
137 1
|
2月前
|
安全 JavaScript Java
Python中None与NoneType的真相:从单例对象到类型系统的深度解析
本文通过10个真实场景,深入解析Python中表示“空值”的None与NoneType。从单例模式、函数返回值,到类型注解、性能优化,全面揭示None在语言设计与实际编程中的核心作用,帮助开发者正确高效地处理“无值”状态,写出更健壮、清晰的Python代码。
168 3
|
2月前
|
Python
解决Python中AttributeError:'image'对象缺少属性'read_file'的问题策略。
通过上述策略综合考虑,您将能够定位问题并确定如何解决它。记住,Python社区很庞大,也很乐于帮助解决问题,因此不要害怕在求助时提供尽可能多的上下文和您已经尝试过的解决方案。
77 0
|
4月前
|
JSON IDE Java
鸿蒙开发:json转对象插件回来了
首先,我重新编译了插件,进行了上传,大家可以下载最新的安装包进行体验了,还是和以前一样,提供了在线版和IDE插件版,两个选择,最新的版本,除了升级了版本,兼容了最新的DevEco Studio ,还做了一层优化,就是针对嵌套对象和属性的生成,使用方式呢,一年前的文章中有过详细的概述,这里呢也简单介绍一下。
147 4
鸿蒙开发:json转对象插件回来了
|
6月前
|
Python
解决Python报错:DataFrame对象没有concat属性的多种方法(解决方案汇总)
总的来说,解决“DataFrame对象没有concat属性”的错误的关键是理解concat函数应该如何正确使用,以及Pandas库提供了哪些其他的数据连接方法。希望这些方法能帮助你解决问题。记住,编程就像是解谜游戏,每一个错误都是一个谜题,解决它们需要耐心和细心。
279 15

推荐镜像

更多