使 Python json 编码器支持 Python 的新数据类

     2023-02-23     276

关键词:

【中文标题】使 Python json 编码器支持 Python 的新数据类【英文标题】:Make the Python json encoder support Python's new dataclasses 【发布时间】:2018-12-19 13:40:23 【问题描述】:

从 Python 3.7 开始,有一种叫做数据类的东西:

from dataclasses import dataclass

@dataclass
class Foo:
    x: str

但是,以下失败:

>>> import json
>>> foo = Foo(x="bar")
>>> json.dumps(foo)
TypeError: Object of type Foo is not JSON serializable

如何使json.dumps()Foo 的实例编码为json objects

【问题讨论】:

【参考方案1】:

就像您可以为 datetime objects 或小数添加对 JSON 编码器的支持一样,您也可以提供自定义编码器子类来序列化数据类:

import dataclasses, json

class EnhancedJSONEncoder(json.JSONEncoder):
        def default(self, o):
            if dataclasses.is_dataclass(o):
                return dataclasses.asdict(o)
            return super().default(o)

json.dumps(foo, cls=EnhancedJSONEncoder)

【讨论】:

重要的是要注意,对于一个名为 Foo 的数据类和一个实例 foo_instance = Foo(...)dataclasses.is_dataclass(Foo)dataclasses.is_dataclass(foo_instance) 评估为 True 导致 TypeError 用于 @987654330 @ if o 是数据类本身,而不是它的实例。 嵌套怎么样 - 例如序列化d= 'a': Foo(1,2), 'b': Foo(3,4) 你如何用这个处理 ndarray 类型的对象? 使用这样的 json 编码器可以很好地处理嵌套 - json 序列化程序将使用它递归地转换子元素。 至于处理 ndarray,像 if isinstance(obj, np.ndarray) and obj.ndim == 1: return obj.tolist() 这样的东西就可以了。虽然请记住,虽然 python 会愉快地序列化 NaNInfinity 之类的东西,但根据 json 规范,这是不合法的,许多解码器会拒绝它,因此可能建议在编码器中处理这些内容。【参考方案2】:

你不能只使用dataclasses.asdict() 函数来转换数据类吗 听写?比如:

>>> @dataclass
... class Foo:
...     a: int
...     b: int
...     
>>> x = Foo(1,2)
>>> json.dumps(dataclasses.asdict(x))
'"a": 1, "b": 2'

【讨论】:

数据类可能是大型结构的深层嵌套部分。通过使用自定义编码器,您可以将json.dumps("obj": [something_that_may_or_may_not_contain_a_dataclass]) 设为 asdict() 将正确处理所有嵌套的数据类,因此,我们得到通常加载到 jison 字符串中的嵌套字典(但是!例如,在加载到字符串之前,必须另外处理 datetime 类型)【参考方案3】:

获取 JSON 化数据类实例的方式

有几个选项可以实现该目标,每个选项的选择都意味着分析哪种方法最适合您的需求:

Standart library: dataclass.asdict

import dataclasses
import json


@dataclass.dataclass
class Foo:
    x: str

foo = Foo(x='1')
json_foo = json.dumps(dataclasses.asdict(foo)) # '"x": "1"'

将其取回数据类实例并非易事,因此您可能希望访问该答案https://***.com/a/53498623/2067976

Marshmallow Dataclass

from dataclasses import field
from marshmallow_dataclass import dataclass


@dataclass
class Foo:
    x: int = field(metadata="required": True)

foo = Foo(x='1') # Foo(x='1')
json_foo = foo.Schema().dumps(foo) # '"x": "1"'

# Back to class instance.
Foo.Schema().loads(json_foo) # Foo(x=1)

作为marshmallow_dataclass 的奖励,您可以在字段本身上使用验证,当有人使用该模式从 json 反序列化对象时将使用该验证。

Dataclasses Json

from dataclasses import dataclass
from dataclasses_json import dataclass_json


@dataclass_json
@dataclass
class Foo:
    x: int

foo = Foo(x='1')
json_foo = foo.to_json() # Foo(x='1')
# Back to class instance
Foo.from_json(json_foo) # Foo(x='1')

此外,除此之外,marshmallow 数据类为您进行了类型转换,而 dataclassses-json(ver.: 0.5.1) 忽略了这一点。

Write Custom Encoder

遵循接受的 Miracle2k 答案并重用自定义 json 编码器。

【讨论】:

感谢marshmallow_dataclass,它确实是通过验证(甚至是 YAML)从 JSON 获取真实对象的好方法。 @mickours 你欢迎 :) 顺便说一句,我没有提到它也适用于 python36(引擎盖下有 dataclass backport) c# + Newtonsoft 多年来,dataclasses_json 是最“自然”的——谢谢! 如果您有兴趣,我也会看看dataclass-wizard。它与dataclasses-json 非常相似,并且效率稍高一些。它还在大多数基本情况下执行类型转换。免责声明:我是这个库的创建者。【参考方案4】:

如果您同意为此使用库,您可以使用dataclasses-json。这是一个例子:

from dataclasses import dataclass

from dataclasses_json import dataclass_json


@dataclass_json
@dataclass
class Foo:
    x: str


foo = Foo(x="some-string")
foo_json = foo.to_json()

它还支持嵌入式数据类 - 如果您的数据类有一个类型为另一个数据类的字段 - 如果所有涉及的数据类都具有 @dataclass_json 装饰器。

【讨论】:

我用嵌入式数据类试过这个,但它不起作用【参考方案5】:

我建议使用to_json() 方法为您的数据类创建一个父类:

import json
from dataclasses import dataclass, asdict

@dataclass
class Dataclass:
    def to_json(self) -> str:
        return json.dumps(asdict(self))

@dataclass
class YourDataclass(Dataclass):
    a: int
    b: int

x = YourDataclass(a=1, b=2)
x.to_json()  # '"a": 1, "b": 2'

如果您要向所有数据类添加其他功能,这将特别有用。

【讨论】:

【参考方案6】:

使用字典解包可以找到更简单的答案on Reddit

>>> from dataclasses import dataclass
>>> @dataclass
... class MyData:
...   prop1: int
...   prop2: str
...   prop3: int
...
>>> d = 'prop1': 5, 'prop2': 'hi', 'prop3': 100
>>> my_data = MyData(**d)
>>> my_data
MyData(prop1=5, prop2='hi', prop3=100)

【讨论】:

这不支持嵌套数据类。 如果 d 来自 json 对象,这可能会引入错误。我们经常假设在payload中添加一个字段并不是一个破坏性的变化,但是在这里,如果你在d中添加一个字段,它会因为“got an unexpected keyword argument 'prop4'”而中断 就原题而言,这是最简单的正确答案。【参考方案7】:

编码dataclassSimpleNamespace 对象的最简单方法是向json.dumps() 提供默认函数,该函数会为无法以其他方式序列化的对象调用,并返回对象__dict__

json.dumps(foo, default=lambda o: o.__dict__)

【讨论】:

这是一个好主意,通常应该适用于序列化具有简单类型的嵌套模型。我猜这不支持的唯一情况是复杂的 Python 类型,如 Enum 或 datetime,或者在 Union 类型中有数据类的边缘情况,如 A | B。尽管如此,这种方法应该适用于一般的简单案例,如原始问题中所述。【参考方案8】:

您还可以在类中实现asdictjson.dumps 方法。在这种情况下,无需将 json.dumps 导入项目的其他部分:


from typing import List
from dataclasses import dataclass, asdict, field
from json import dumps


@dataclass
class TestDataClass:
    """
    Data Class for TestDataClass
    """
    id: int
    name: str
    tested: bool = False
    test_list: List[str] = field(default_factory=list)

    @property
    def __dict__(self):
        """
        get a python dictionary
        """
        return asdict(self)

    @property
    def json(self):
        """
        get the json formated string
        """
        return dumps(self.__dict__)


test_object_1 = TestDataClass(id=1, name="Hi")
print(test_object_1.__dict__)
print(test_object_1.json)

输出:

'id': 1, 'name': 'Hi', 'tested': False, 'test_list': []
"id": 1, "name": "Hi", "tested": false, "test_list": []

你也可以创建一个父类来继承方法:

from typing import List
from dataclasses import dataclass, asdict, field
from json import dumps


@dataclass
class SuperTestDataClass:

    @property
    def __dict__(self):
        """
        get a python dictionary
        """
        return asdict(self)

    @property
    def json(self):
        """
        get the json formated string
        """
        return dumps(self.__dict__)


@dataclass
class TestDataClass(SuperTestDataClass):
    """
    Data Class for TestDataClass
    """
    id: int
    name: str
    tested: bool = False
    test_list: List[str] = field(default_factory=list)


test_object_1 = TestDataClass(id=1, name="Hi")
print(test_object_1.__dict__)
print(test_object_1.json)


【讨论】:

【参考方案9】:

好的,这就是我遇到类似情况时所做的。

    创建一个自定义字典工厂,将嵌套数据类转换为字典。

    def myfactory(数据): return dict(x for x in data if x[1] is not None)

    如果 foo 是您的 @dataclass,那么只需提供您的字典工厂以使用“myfactory()”方法:

    fooDict = asdict(foo, dict_factory=myfactory)

    将 fooDict 转换为 json

    fooJson = json.dumps(fooDict)

这应该可以!!

【讨论】:

【参考方案10】:

dataclass-wizard 是一个适合您的现代选项。它支持复杂类型,例如日期和时间、typing 模块中的大多数泛型,以及嵌套数据类结构。

PEP 585 和 604 中引入的“新样式”注释可以通过 __future__ 导入移植回 Python 3.7,如下所示。

from __future__ import annotations  # This can be removed in Python 3.10
from dataclasses import dataclass, field
from dataclass_wizard import JSONWizard


@dataclass
class MyClass(JSONWizard):
    my_str: str | None
    is_active_tuple: tuple[bool, ...]
    list_of_int: list[int] = field(default_factory=list)


string = """

  "my_str": 20,
  "ListOfInt": ["1", "2", 3],
  "isActiveTuple": ["true", false, 1]

"""

instance = MyClass.from_json(string)
print(repr(instance))
# MyClass(my_str='20', is_active_tuple=(True, False, True), list_of_int=[1, 2, 3])

print(instance.to_json())
# '"myStr": "20", "isActiveTuple": [true, false, true], "listOfInt": [1, 2, 3]'

# True
assert instance == MyClass.from_json(instance.to_json())

您可以使用pip 安装数据类向导:

$ pip install dataclass-wizard

一些背景信息:

对于序列化,它使用dataclasses.asdict 的略微修改(更高效)的实现。在将 JSON 反序列化为数据类实例时,它第一次迭代数据类字段并为每个带注释的类型生成一个解析器,这使得反序列化过程多次运行时效率更高。

免责声明:我是这个库的创建者(和维护者)。

【讨论】:

如何使 JSON 对象可序列化 [重复]

...2013-09-0407:38:37【问题描述】:有没有办法在不使用自定义编码器的情况下序列化python类?我尝试了以下方法,但出现错误:TypeError:helloisnotJSONserializable这很奇怪,因为“hello”是一个字符串。classMyObj(object) 查看详情

使用常规编码器使对象 JSON 可序列化

】使用常规编码器使对象JSON可序列化【英文标题】:MakingobjectJSONserializablewithregularencoder【发布时间】:2013-08-3023:01:12【问题描述】:JSON序列化自定义不可序列化对象的常规方法是继承json.JSONEncoder,然后将自定义编码器传递给js... 查看详情

pytho的基本语法

python3默认的编码格式:utf-8 标识符: 1、可以是数字,字母,下划线组成2、不能以数字开头3、不能以保留字命名(保留字即是关键字,比如"print")4、区分大小写注释:python中单行注释以#开头 实例: #第一个注释pri... 查看详情

覆盖继承的默认支持对象(如 dict、list)的嵌套 JSON 编码

...像它们一样工作。然而,当我想将它们编码为JSON(使用Python)时,我希望它们以一种我可 查看详情

2018年4月10日笔记

Python的编码常见的支持中文的编码有:utf-8,gbk,gb2312常见术语:decode(解码),encode(编码)在Python2中,不写python代码抬头,在print中文字符时会出现乱码;在Python3中不会出现这种情况。因为Python2默认将代码文件内容用ASCII编码处理,... 查看详情

如何使vlc支持fdk-aac编码(windows平台

...通过修改VLC的编译脚本,并加入fdkaac库来使用VLC支持fdkaac编码器。我是在64位的ubuntu16.04下编译的,vlc的编译方法参考VLC官网的编译文档。 1 修改vlc/e 查看详情

python解码(编码)不会使保存的文件饿死(代码片段)

查看详情

在 Python 中声明编码 [重复]

】在Python中声明编码[重复]【英文标题】:DeclaringencodinginPython[duplicate]【发布时间】:2012-08-2715:02:54【问题描述】:我想使用以下代码在python中拆分一个字符串:means="a،b،c"lst=means.split("،")但我收到此错误消息:语法错误:第2行... 查看详情

如何使python对象json序列化?

】如何使python对象json序列化?【英文标题】:Howtomakeapythonobjectjson-serialized?【发布时间】:2019-07-2301:57:15【问题描述】:我想serializeapythonobject,在将它保存到mysql(基于DjangoORM)后,我想获取它并将这个对象传递给需要这种对象... 查看详情

python标准库之json编码和解码器『详解』(代码片段)

...使用「重点」🧊1、序列化操作2、反序列化操作五、编码器和解码器🥃六、异常🧃参考资料💖相关博客😋一、Pythonjson库介绍JSON 查看详情

python之json编码

...组,可以存放多个对象(5)字符串使用双引号 二、Python类型与json类型转换1 查看详情

如何使 Python 中的 json.dumps 忽略不可序列化的字段

】如何使Python中的json.dumps忽略不可序列化的字段【英文标题】:Howtomakejson.dumpsinPythonignoreanon-serializablefield【发布时间】:2019-01-1110:35:40【问题描述】:我正在尝试使用Construct2.9库对解析一些二进制数据的输出进行序列化。我想... 查看详情

我应该等待 Django 开始支持 Python 3 吗?

】我应该等待Django开始支持Python3吗?【英文标题】:ShouldIwaitforDjangotostartsupportingPython3?【发布时间】:2011-05-1506:53:07【问题描述】:我有一个令我非常兴奋的网站创意,而且我喜欢Python。所以,我对使用Django很感兴趣。不过,... 查看详情

如何使vlc支持fdk-aac编码windows平台

...通过修改VLC的编译脚本,并加入fdkaac库来使用VLC支持fdkaac编码器。我是在64位的ubuntu16.04下编译的,vlc的编译方法参考VLC官网的编译文档。1修改vlc/extras/package/win32/configure.sh,加入--enable-fdkaac,这样VLC就会编译fdkaac这个模块。执行... 查看详情

Python json.loads ValueError,需要分隔符

】Pythonjson.loadsValueError,需要分隔符【英文标题】:Pythonjson.loadsValueError,expectingdelimiter【发布时间】:2015-03-2904:22:22【问题描述】:我将一个postgres表提取为json。输出文件包含如下行:"data":"test":1,"hello":"Ihave\\"!","id":4现在我需要... 查看详情

JSON 字符编码 - 浏览器是不是很好地支持 UTF-8 或者我应该使用数字转义序列?

】JSON字符编码-浏览器是不是很好地支持UTF-8或者我应该使用数字转义序列?【英文标题】:JSONcharacterencoding-isUTF-8well-supportedbybrowsersorshouldIusenumericescapesequences?JSON字符编码-浏览器是否很好地支持UTF-8或者我应该使用数字转义序列... 查看详情

Python 请求返回 JSON 解码器错误

】Python请求返回JSON解码器错误【英文标题】:PythonrequestreturningaJSONDecoderError【发布时间】:2019-09-2519:02:27【问题描述】:我正在尝试从网站中提取数据。我正在使用Python请求:users=requests.get(\'websitenamehere\',headers=headers).json()我收... 查看详情

在 JSON 中编码嵌套的 python 对象

】在JSON中编码嵌套的python对象【英文标题】:EncodingnestedpythonobjectinJSON【发布时间】:2011-07-0619:33:21【问题描述】:我想用JSON编码对象。但是,我不知道如何在没有字符串转义的情况下进行输出。importjsonclassAbc:def__init__(self):self... 查看详情