如何在Python中覆盖数据类属性的名称?

Mil*_*vić 10 python json python-dataclasses

我正在使用dataclass解析(HTTP 请求/响应)JSON 对象,今天我遇到了一个问题,需要在我的类中进行转换/别名属性名称。

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


@dataclass
class Foo:
    foo_name: str # foo_name -> FOO NAME


@dataclass
class Bar:
    bar_name: str # bar_name -> barName


@dataclass
class Baz:
    baz_name: str # baz_name -> B A Z
    baz_foo: List[Foo] # baz_foo -> BAZ FOO
    baz_bar: List[Bar] # baz_bar -> BAZ BAR
Run Code Online (Sandbox Code Playgroud)

现在:

# encode
baz_e = Baz("name", [{"foo_name": "one"}, {"foo_name": "two"}], [{"bar_name": "first"}])
json_baz_e = json.dumps(asdict(baz_e))
print(json_baz_e)
# {"baz_name": "name", "baz_foo": [{"foo_name": "one"}, {"foo_name": "two"}], "baz_bar": [{"bar_name": "first"}]}


# decode
json_baz_d = {
    "baz_name": "name", 
    "baz_foo": [{"foo_name": "one"}, {"foo_name": "two"}], 
    "baz_bar":[{"bar_name": "first"}]
}
baz_d = Baz(**json_baz_d) # back to class instance
print(baz_d)
# Baz(baz_name='name', baz_foo=[{'foo_name': 'one'}, {'foo_name': 'two'}], baz_bar=[{'bar_name': 'first'}])
Run Code Online (Sandbox Code Playgroud)

预期的:

# encode
baz_e = Baz("name", [{"FOO NAME": "one"}, {"FOO NAME": "two"}], [{"barName": "first"}])
json_baz_e = json.dumps(asdict(baz_e))


# decode
json_baz_d = {
    "B A Z": "name", 
    "BAZ FOO": [{"FOO NAME": "one"}, {"FOO NAME": "two"}], 
    "BAZ BAR":[{"barName": "first"}]
}
baz_d = Baz(**json_baz_d) # back to class instance
Run Code Online (Sandbox Code Playgroud)

是唯一的解决方案dataclasses-json,还是仍然有可能没有额外的库?

rv.*_*tch 7

您当然可以使用dataclasses-json它,但是如果您不需要棉花糖模式的优势,您可能可以使用替代解决方案,例如dataclass-wizard,它类似于构建在数据类之上的 JSON 序列化库。它支持别名此处需要的另一个好处是,除了 Python < 3.10 的模块之外,它没有任何 Python stdlib 之外的依赖项typing-extensions

几个选项可用于指定别名字段映射,但在下面的示例中,我选择了两个选项来说明:

  • json_field,它可以被认为是一个别名dataclasses.field
  • json_key_to_field可以在数据类的元配置中指定的映射
from dataclasses import dataclass
from typing import List

from dataclass_wizard import JSONWizard, json_field


@dataclass
class Foo:
    # pass all=True, so reverse mapping (field -> JSON) is also added
    foo_name: str = json_field('FOO NAME', all=True)


@dataclass
class Bar:
    # default key transform is `camelCase`, so alias is not needed here
    bar_name: str


@dataclass
class Baz(JSONWizard):

    class _(JSONWizard.Meta):
        json_key_to_field = {
            # Pass '__all__', so reverse mapping (field -> JSON) is also added
            '__all__': True,
            'B A Z': 'baz_name',
            'BAZ FOO': 'baz_foo',
            'BAZ BAR': 'baz_bar'
        }

    baz_name: str
    baz_foo: List[Foo]
    baz_bar: List[Bar]


# encode
baz_e = Baz("name", [Foo('one'), Foo('two')], [Bar('first')])
json_baz_d = baz_e.to_dict()

print(json_baz_d)
# {'B A Z': 'name', 'BAZ FOO': [{'FOO NAME': 'one'}, {'FOO NAME': 'two'}], 'BAZ BAR': [{'barName': 'first'}]}

# decode
baz_d = Baz.from_dict(json_baz_d)  # back to class instance

print(repr(baz_d))
# > Baz(baz_name='name', baz_foo=[Foo(foo_name='one'), Foo(foo_name='two')], baz_bar=[Bar(bar_name='first')])

# True
assert baz_e == baz_d
Run Code Online (Sandbox Code Playgroud)

注意:我注意到我想指出的一件明显的事情,因为它似乎没有导致预期的行为。在上面的问题中,您似乎正在实例化一个Baz实例,如下所示:

baz_e = Baz("name", [{"foo_name": "one"}, {"foo_name": "two"}], [{"bar_name": "first"}])
Run Code Online (Sandbox Code Playgroud)

但请注意,baz_foo在本例中,该字段的值是 Python 对象的列表,dict而不是Foo实例的列表。为了解决这个问题,在上面的解决方案中,我将{"foo_name": "one"}example 更改为Foo('one').