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

Make the Python json encoder support Python's new dataclasses

从 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

就像您可以为 datetime objects 或 Decimals 添加对 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)

不能直接用dataclasses.asdict()函数转换dataclass吗 听写?类似于:

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

如果您可以为此使用库,则可以使用 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 装饰器。

获取 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"}'

将其恢复为数据类实例并非易事,因此您可能需要访问该答案

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

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

遵循已接受的 miracle2k 答案并重复使用自定义 json 编码器。

使用字典解包

可以找到更简单的答案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)

我建议使用 to_json() 方法为您的数据class创建父级 class:

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}'

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

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

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

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

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

    fooDict = asdict(foo, dict_factory=myfactory)

  3. 将 fooDict 转换为 json

    fooJson = json.dumps(fooDict)

这应该行得通!!

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

PEP 585 and 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

一些背景信息:

For serialization, it uses a slightly modified (a bit more efficient) implementation of dataclasses.asdict. When de-serializing JSON to a dataclass instance, the first time it iterates over the dataclass fields and generates a parser for each annotated type, which makes it more efficient when the de-serialization process is run multiple times.

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

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

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

您还可以在 class 中实现 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": []}

您还可以创建一个父级 class 来继承这些方法:

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)