数据类与可json字典之间的简单灵活转换。
项目描述
dataclass-jsonable
数据类与可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}
-
Enum
和IntEnum
通过.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
允许我们传递default
或default_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__
。如果一个字段没有声明default
或default_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 给
encoder
和decoder
提供了更好的别名:to_json
和from_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 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 6bcfa8f31bb06b847cfe007ddf0c976d220c36bc28fe47660ee71a673b90347c |
|
MD5 | 37ea794d2e741d718ae9782cb6750b99 |
|
BLAKE2b-256 | 9113ebace88a4f9cdead6e8489641ac188242a55da0e8673d877e41ec92809c3 |