跳转到主要内容

数据类与可json字典之间的简单灵活转换。

项目描述

dataclass-jsonable

dataclass-jsonable ci

中文说明

数据类与可json字典之间的简单灵活转换。

它将数据类映射到可json字典,但不映射到json字符串。

功能

  • 易于使用。
  • 支持常见的类型注解。
  • 支持递归转换。
  • 支持字段级和数据类级覆盖。

安装

要求:Python >= 3.7

通过 pip 安装

pip install dataclass-jsonable

快速示例

from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal
from enum import IntEnum
from typing import List
from dataclass_jsonable import J

class Color(IntEnum):
    BLACK = 0
    BLUE = 1
    RED = 2

@dataclass
class Pen(J):
    color: Color
    price: Decimal
    produced_at: datetime

@dataclass
class Box(J):
    pens: List[Pen]

box = Box(pens=[Pen(color=Color.BLUE, price=Decimal("20.1"), produced_at=datetime.now())])

# Encode to a jsonable dictionary.
d = box.json()
print(d)  # {'pens': [{'color': 1, 'price': '20.1', 'produced_at': 1660023062}]}

# Construct dataclass from a jsonable dictionary.
print(Box.from_json(d))

API只有两个: .json().from_json()

内置支持的类型

  • bool, int, float, str, None 以其原始形式编码。

    @dataclass
    class Obj(J):
        a: int
        b: str
        c: bool
        d: None
    
    Obj(a=1, b="b", c=True, d=None).json()
    # => {'a': 1, 'b': 'b', 'c': True, 'd': None}
    
  • Decimal 编码为 str

    @dataclass
    class Obj(J):
        a: Decimal
    
    Obj(a=Decimal("3.1")).json()  # => {'a': '3.1'}
    
  • datetime 通过 .timestamp() 方法编码为时间戳整数。timedelta 通过 .total_seconds() 方法编码为整数。

    @dataclass
    class Obj(J):
        a: datetime
        b: timedelta
    
    Obj(a=datetime.now(), b=timedelta(minutes=1)).json()
    # => {'a': 1660062019, 'b': 60}
    
  • EnumIntEnum 通过 .value 属性编码为其值。

    @dataclass
    class Obj(J):
        status: Status
    
    Obj(status=Status.DONE).json()  # => {'status': 1}
    
  • Any 根据 type 进行编码。

    @dataclass
    class Obj(J):
        a: Any
    
    Obj(1).json()  # {'a': 1}
    Obj("a").json()  # {'a': 'a'}
    Obj.from_json({"a": 1})  # Obj(a=1)
    
  • Optional[X] 受支持,但 Union[X, Y, ...] 不支持。

    @dataclass
    class Obj(J):
        a: Optional[int] = None
    
    Obj(a=1).json()  # => {'a': 1}
    
  • List[X], Tuple[X], Set[X] 都编码为 list

    @dataclass
    class Obj(J):
        a: List[int]
        b: Set[int]
        c: Tuple[int, str]
        d: Tuple[int, ...]
    
    Obj(a=[1], b={2, 3}, c=(4, "5"), d=(7, 8, 9)).json())
    # => {'a': [1], 'b': [2, 3], 'c': [4, '5'], 'd': [7, 8, 9]}
    
    Obj.from_json({"a": [1], "b": [2, 3], "c": [4, "5"], "d": [7, 8, 9]}))
    # => Obj(a=[1], b={2, 3}, c=(4, '5'), d=(7, 8, 9))
    
  • Dict[str, X] 编码为 dict

    @dataclass
    class Obj(J):
        a: Dict[str, int]
    Obj(a={"x": 1}).json()  # => {'a': {'x': 1}}
    Obj.from_json({"a": {"x": 1}}) # => Obj(a={'x': 1})
    
  • 嵌套或递归 JSONAble(或 J)数据类。

    @dataclass
    class Elem(J):
        k: str
    
    @dataclass
    class Obj(J):
        a: List[Elem]
    
    Obj([Elem("v")]).json()  # => {'a': [{'k': 'v'}]}
    Obj.from_json({"a": [{"k": "v"}]})  # Obj(a=[Elem(k='v')])
    
  • 延迟注解(PEP 563 中的 ForwardRef)。

    @dataclass
    class Node(J):
        name: str
        left: Optional["Node"] = None
        right: Optional["Node"] = None
    
    root = Node("root", left=Node("left"), right=Node("right"))
    root.json()
    # {'name': 'root', 'left': {'name': 'left', 'left': None, 'right': None}, 'right': {'name': 'right', 'left': None, 'right': None}}
    

如果这些内置默认转换行为不符合您的需求,或您的类型不在列表中,您可以使用下面介绍的 json_options 来自定义它。

自定义/覆盖示例

我们可以使用 json_options 覆盖默认的转换行为,它使用数据类字段的元数据用于字段级自定义,命名空间为 j

以下伪代码给出模式

from dataclasses import field
from dataclass_jsonable import json_options

@dataclass
class Struct(J):
    attr: T = field(metadata={"j": json_options(**kwds)})

关于 json_options 的示例列表

  • 指定一个自定义字典键,覆盖默认字段的名称

    @dataclass
    class Person(J):
        attr: str = field(metadata={"j": json_options(name="new_attr")})
    Person(attr="value").json() # => {"new_attr": "value"}
    

    此外,我们可以使用一个函数来指定自定义字典键。这可能在与类级别的 __default_json_options__ 属性(如下所示)一起工作时很有用。

    @dataclass
    class Obj(J):
        simple_value: int = field(metadata={"j": json_options(name_converter=to_camel_case)})
    Obj(simple_value=1).json()  # => {"simpleValue": 1}
    

    在将字典转换为 dataclass 时,我们可以指定自定义字段名称转换器。

    @dataclass
    def Person(J):
      name: str = field(
            metadata={
                "j": json_options(
                    name_converter=lambda x: x.capitalize(),
                    name_inverter=lambda x: "nickname",
              )
          }
      )
    

    如上定义的 Person,它将转换为 {"Name": "Jack"} 的字典,并且可以从 {"nickname": "Jack"} 加载。

  • 如果字段的值为空,则省略该字段。

    @dataclass
    class Book(J):
        name: str = field(metadata={"j": json_options(omitempty=True)})
    Book(name="").json() # => {}
    

    此外,我们可以通过选项 omitempty_tester 指定“空”的含义。

    @dataclass
    class Book(J):
        attr: Optional[str] = field(
            default=None,
            metadata={
                # By default, we test `empty` using `not x`.
                "j": json_options(omitempty=True, omitempty_tester=lambda x: x is None)
            },
        )
    
    Book(attr="").json()  # => {'attr': ''}
    Book(attr=None).json()  # => {}
    
  • 始终跳过字段。这样我们就可以阻止某些“私有”字段被导出。

    @dataclass
    class Obj(J):
        attr: str = field(metadata={"j": json_options(skip=True)})
    
    Obj(attr="private").json() # => {}
    
  • 始终以不编码也不解码的方式保留字段,这防止了默认的编码/解码行为。

    @dataclass
    class Obj(J):
        timestamp: datetime = field(metadata={"j": json_options(keep=True)})
    
    Obj(timestamp=datetime.now()).json() # =>  {'timestamp': datetime.datetime(2023, 9, 5, 14, 54, 24, 679103)}
    
  • dataclasses 的 field 允许我们传递 defaultdefault_factory 参数来设置默认值。

    @dataclass
    class Obj(J):
        attr: List[str] = field(default_factory=list, metadata={"j": json_options(**kwds)})
    

    dataclass-jsonable 中还有一个选项 default_before_decoding,它指定在字典中缺失键时的默认值。有时这种方式更简洁。

    @dataclass
    class Obj(J):
        updated_at: datetime = field(metadata={"j": json_options(default_before_decoding=0)})
    
    Obj.from_json({})  # => Obj(updated_at=datetime.datetime(1970, 1, 1, 8, 0))
    

    dataclass-jsonable 还引入了一个类级别的类似选项 __default_factory__。如果一个字段没有声明 defaultdefault_factory,并且没有使用 default_before_decoding 选项,这个函数将根据其类型生成一个默认值,以防止引发“缺少位置参数”的 TypeError。

    from dataclass_jsonable import J, zero
    
    @dataclass
    class Obj(J):
        __default_factory__ = zero
    
        n: int
        s: str
        k: List[str]
    
    Obj.from_json({})  # => Obj(n=0, s='', k=[])
    
  • 覆盖默认的编码器和解码器。

    这样,您可以在字段级别完全控制如何编码和解码。

    @dataclass
    class Obj(J):
        elems: List[str] = field(
            metadata={
                "j": json_options(
                    encoder=lambda x: ",".join(x),
                    decoder=lambda x: x.split(","),
                )
            }
        )
    
    Obj(elems=["a", "b", "c"]).json()  # => {'elems': 'a,b,c'}
    Obj.from_json({"elems": "a,b,c"})  # => Obj(elems=['a', 'b', 'c'])
    

    以下关于 datetime 的代码片段是一个非常常见的例子,您可能希望进行 ISO 格式的日期时间转换,而不是时间戳整数。

    @dataclass
    class Record(J):
        created_at: datetime = field(
            default_factory=datetime.now,
            metadata={
                "j": json_options(
                    encoder=datetime.isoformat,
                    decoder=datetime.fromisoformat,
                )
            },
        )
    
    Record().json()  # => {'created_at': '2022-08-09T23:23:02.543007'}
    

    自 0.1.1 版本以来,dataclass-jsonable 给 encoderdecoder 提供了更好的别名:to_jsonfrom_json

    @dataclass
    class Obj(J):
        elems: List[str] = field(
            metadata={
                "j": json_options(
                    to_json=lambda x: ",".join(x),  # Alias for encoder
                    from_json=lambda x: x.split(","),  # Alias for decoder
                )
            }
        )
    
    Obj(elems=["a", "b", "c"]).json()  # => {'elems': 'a,b,c'}
    Obj.from_json({"elems": "a,b,c"})  # => Obj(elems=['a', 'b', 'c'])
    
  • 对于某些非常特定的场景,我们可能需要在解码之前执行一个钩子函数,例如,要解码的数据是序列化的 JSON 字符串,但我们仍然想使用内置的解码器函数而不是创建一个新的解码器。

    import json
    
    @dataclass
    class Obj(J):
        data: Dict[str, Any] = field(metadata={"j": json_options(before_decoder=json.loads)})
    
    Obj.from_json({"data": '{"k": "v"}'})
    # => Obj(data={'k': 'v'})
    
  • 在类级别自定义默认行为。

    如果字段级别没有明确设置选项,将尝试使用类级别提供的 __default_json_options__

    @dataclass
    class Obj(J):
        __default_json_options__ = json_options(omitempty=True)
    
        a: Optional[int] = None
        b: Optional[str] = None
    
    Obj(b="b").json() # => {'b': 'b'}
    
    @dataclass
    class Obj(J):
        __default_json_options__ = json_options(name_converter=to_camel_case)
    
        status_code: int
        simple_value: str
    
    Obj2(status_code=1, simple_value="simple").json()
    # => {"statusCode": 1, "simpleValue": "simple"}
    

调试

它提供了一个方法 obj._get_origin_json(),它返回通过 from_json() 构建实例 obj 的原始 JSON 字典。

d = {"a": 1}
obj = Obj.from_json(d)
obj._get_origin_json()
# => {"a": 1}

许可证

BSD。

项目详情


下载文件

下载适合您平台的文件。如果您不确定选择哪个,请了解有关 安装包 的更多信息。

源分布

dataclass-jsonable-0.1.3.tar.gz (11.6 kB 查看哈希值)

上传时间

由以下支持

AWS AWS 云计算和安全赞助商 Datadog Datadog 监控 Fastly Fastly CDN Google Google 下载分析 Microsoft Microsoft PSF 赞助商 Pingdom Pingdom 监控 Sentry Sentry 错误日志 StatusPage StatusPage 状态页面