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

从 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目标

107837 次浏览

就像你可以添加支持 jSON 编码器的 abc1或 Decimals,你也可以提供一个自定义的编码器子类序列化数据类:

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)

您不能仅仅使用 dataclasses.asdict()函数来转换数据类吗 结论? 比如:

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

如果你不介意使用库,你可以使用 数据类 -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修饰符。

获取 JSONfied 数据类实例的方法

实现这个目标有几个选择,每个选择都意味着分析哪种方法最适合你的需要:

标准库: 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://stackoverflow.com/a/53498623/2067976

棉花糖数据类

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 反序列化对象时,将使用该验证。

数据类 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')

另外,除了注意到棉花糖数据类为您执行了类型转换,而 datacassses-json (ver. : 0.5.1)忽略了这一点之外。

编写自定义编码器

按照接受的神迹2k 答案和重用自定义 json 编码器。

使用字典解压缩可以找到一个更简单的答案 在 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)

我建议使用 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}'

如果您有其他功能要添加到所有数据类中,这尤其有用。

这是我在类似情况下所做的。

  1. 创建将嵌套数据类转换为 dictionary 的自定义字典工厂。

    Def myFactory (数据) : 返回 dict (如果 x [1]不是 Nothing,x 表示数据中的 x)

  2. 如果 foo 是您的@datacclass,那么只需提供您的字典工厂来使用“ myFactory ()”方法:

    FooDect = asdict (foo,dict _ Factory = myFactory)

  3. 将 fooDect 转换为 json

    FooJson = json.dump (fooDect)

这应该能行!

Datacclass-Wizard 是一个现代的选项,可以为您工作。它支持日期和时间等复杂类型、来自 typing模块的大多数泛型以及 嵌套的数据类结构。

PEPs 585604中引入的“新样式”注释可以通过 __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 反序列化为数据类实例时,第一次迭代数据类字段并为每个带注释的类型生成一个解析器,这使得反序列化过程多次运行时效率更高。

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

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

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

还可以在类中实现 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)




提供 json 格式化方法的数据类

import json
from dataclasses import dataclass


@dataclass
class Foo:
x: str
   

def to_json(self):
return json.dumps(self.__dict__)


Foo("bar").to_json()
>>> '{"x":"bar"}'