Introduction
pyserde
はdataclassesベースのシンプルで強力なシリアライゼーションライブラリで、クラスをJSONやYAML等の様々なデータフォーマットに簡単に効率的に変換可能になります。
クラスに@serde
デコレータを付け、PEP484形式でフィールドに型アノテーションを付けます。
from serde import serde
@serde
class Foo:
i: int
s: str
f: float
b: bool
すると、Foo
クラスは以下のようにJSONにシリアライズ出来るようになります。
>>> from serde.json import to_json
>>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
'{"i":10,"s":"foo","f":100.0,"b":true}'
また、JSONからFoo
クラスにデシリアライズも出来るようになります。
>>> from serde.json import from_json
>>> from_json(Foo, '{"i": 10, "s": "foo", "f": 100.0, "b": true}')
Foo(i=10, s='foo', f=100.0, b=True)
Next Steps
Getting Started
インストール
PyPIからpyserdeをインストールしてください。pyserdeにはPython>=3.9が必要です。
pip install pyserde
poetryを使用している場合は、以下のコマンドを実行してください。
poetry add pyserde
JSONとPickle以外のデータ形式を扱う場合は、追加の依存関係をインストールする必要があります。
適切なデータ形式で動作するには、msgpack
、toml
、yaml
の追加パッケージをインストールしてください。
使用しない場合はスキップして構いません。
例えば、TomlとYAMLを使用する場合は以下のようにします。
pip install "pyserde[toml,yaml]"
poetryを使用している場合
poetry add pyserde -E toml -E yaml
一度にすべてをインストールする場合
pip install "pyserde[all]"
poetryを使用している場合
poetry add pyserde -E all
利用可能な追加パッケージ
all
:msgpack
、toml
、yaml
、numpy
、orjson
、sqlalchemy
をインストールmsgpack
:msgpack をインストールtoml
:tomli と tomli-w をインストール- 注記:python 3.11以降は tomllib を使用
yaml
:pyyaml をインストールnumpy
:numpy をインストールorjson
:orjson をインストールsqlalchemy
:sqlalchemy をインストール
最初のpyserdeクラスを定義
pyserdeの@serde
デコレータを使用してクラスを定義します。
モジュール名が pyserde
ではなく serde
であることに注意してください。
pyserde
は標準ライブラリのdataclasses
モジュールに大きく依存しています。
そのため、dataclassに慣れていない場合はまずdataclassesのドキュメントを読むことをおすすめします。
from serde import serde
@serde
class Foo:
i: int
s: str
f: float
b: bool
クラスがPythonインタプリタにロードされると、pyserdeは@serde
によって(デ)シリアライズに必要なメソッドを生成します。
コード生成は一度だけ行われ、クラスを使用する際にオーバーヘッドはありません。
これにより、クラスはpyserdeがサポートするすべてのデータ形式でシリアライズおよびデシリアライズ可能になります。
注記: シリアライズまたはデシリアライズの機能のみが必要な場合、
@serde
デコレータの代わりに@serialize
や@deserialize
を使用できます。例:シリアライズのみを行う場合、
@serialize
デコレータを使用できます。しかし、Foo
のデシリアライズAPI(例:from_json
)を呼び出すとエラーが発生します。from serde import serialize @serialize class Foo: i: int s: str f: float b: bool
PEP585とPEP604
python>=3.9用のPEP585スタイルのアノテーションと、python>=3.10用のPEP604 Unionオペレータがサポートされています。
PEP585とPEP604を使用すると、pyserdeクラスをきれいに書くことができます。
@serde
class Foo:
a: int
b: list[str]
c: tuple[int, float, str, bool]
d: dict[str, list[tuple[str, int]]]
e: str | None
pyserdeクラスの使用
次に、pyserdeの(デ)シリアライズAPIをインポートします。
JSONの場合は以下のようにします。
from serde.json import from_json, to_json
to_json
を使用してオブジェクトをJSONにシリアライズします。
f = Foo(i=10, s='foo', f=100.0, b=True)
print(to_json(f))
from_json
にFoo
クラスとJSON文字列を渡して、JSONをオブジェクトにデシリアライズします。
s = '{"i": 10, "s": "foo", "f": 100.0, "b": true}'
print(from_json(Foo, s))
以上です!
pyserdeには他にも多くの機能があります。興味があれば、残りのドキュメントをお読みください。
💡 ヒント:どのタイプチェッカーを使用すべきか?
pyserdeはPEP681 dataclass_transformに依存しています。
2024年1月現在、mypyはdataclass_transformを完全にはサポートしていません。
私の個人的なおすすめはpyrightです。
Data Formats
pyserdeは、dict
、tuple
、JSON
、YAML
、TOML
、MsgPack
、Pickle
などのさまざまなデータ形式をシリアライズおよびデシリアライズできます。
各APIは追加のキーワード引数を取ることができ、これらの引数はpyserdeで使用されるベースパッケージへと渡されます。
例えば、YAMLでフィールドの順序を保持したい場合、serde.yaml.to_yaml
にsort_key=True
を渡すことができます。
serde.yaml.to_yaml(foo, sort_key=True)
sort_key=True
はyaml.safedumpに渡されます。
dict
>>> from serde import to_dict, from_dict
>>> to_dict(Foo(i=10, s='foo', f=100.0, b=True))
from_dict(Foo, {"i": 10, "s": "foo", "f": 100.0, "b": True})
>>> from_dict(Foo, {"i": 10, "s": "foo", "f": 100.0, "b": True})
Foo(i=10, s='foo', f=100.0, b=True)
詳細はserde.to_dict / serde.from_dictをご覧ください。
tuple
>>> from serde import to_tuple, from_tuple
>>> to_tuple(Foo(i=10, s='foo', f=100.0, b=True))
(10, 'foo', 100.0, True)
>>> from_tuple(Foo, (10, 'foo', 100.0, True))
Foo(i=10, s='foo', f=100.0, b=True)
詳細はserde.to_tuple / serde.from_tupleをご覧ください。
JSON
>>> from serde.json import to_json, from_json
>>> to_json(Foo(i=10, s='foo', f=100.0, b=True))
'{"i":10,"s":"foo","f":100.0,"b":true}'
>>> from_json(Foo, '{"i": 10, "s": "foo", "f": 100.0, "b": true}')
Foo(i=10, s='foo', f=100.0, b=True)
詳細はserde.json.to_json / serde.json.from_jsonをご覧ください。
Yaml
>>> from serde.yaml import from_yaml, to_yaml
>>> to_yaml(Foo(i=10, s='foo', f=100.0, b=True))
b: true
f: 100.0
i: 10
s: foo
>>> from_yaml(Foo, 'b: true\nf: 100.0\ni: 10\ns: foo')
Foo(i=10, s='foo', f=100.0, b=True)
詳細はserde.yaml.to_yaml / serde.yaml.from_yamlをご覧ください。
Toml
>>> from serde.toml from_toml, to_toml
>>> to_toml(Foo(i=10, s='foo', f=100.0, b=True))
i = 10
s = "foo"
f = 100.0
b = true
>>> from_toml(Foo, 'i = 10\ns = "foo"\nf = 100.0\n
b = true')
Foo(i=10, s='foo', f=100.0, b=True)
詳細はserde.toml.to_toml / serde.toml.from_tomlをご覧ください。
MsgPack
>>> from serde.msgpack import from_msgpack, to_msgpack
>>> to_msgpack(Foo(i=10, s='foo', f=100.0, b=True))
b'\x84\xa1i\n\xa1s\xa3foo\xa1f\xcb@Y\x00\x00\x00\x00\x00\x00\xa1b\xc3'
>>> from_msgpack(Foo, b'\x84\xa1i\n\xa1s\xa3foo\xa1f\xcb@Y\x00\x00\x00\x00\x00\x00\xa1b\xc3')
Foo(i=10, s='foo', f=100.0, b=True)
詳細はserde.msgpack.to_msgpack / serde.msgpack.from_msgpackをご覧ください。
Pickle
v0.9.6で新しく追加されました
>>> from serde.pickle import from_pickle, to_pickle
>>> to_pickle(Foo(i=10, s='foo', f=100.0, b=True))
b"\x80\x04\x95'\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x01i\x94K\n\x8c\x01s\x94\x8c\x03foo\x94\x8c\x01f\x94G@Y\x00\x00\x00\x00\x00\x00\x8c\x01b\x94\x88u."
>>> from_pickle(Foo, b"\x80\x04\x95'\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x01i\x94K\n\x8c\x01s\x94\x8c\x03foo\x94\x8c\x01f\x94G@Y\x00\x00\x00\x00\x00\x00\x8c\x01b\x94\x88u.")
Foo(i=10, s='foo', f=100.0, b=True)
詳細はserde.pickle.to_pickle / serde.pickle.from_pickleをご覧ください。
新しいデータ形式のサポートが必要ですか?
pyserde をできるだけシンプルに保つため、XML のようなデータフォーマットをサポートする予定はありません。
新しいデータ形式のサポートが必要な場合は、別のPythonパッケージを作成することをお勧めします。
データ形式がdictやtupleに変換可能であれば、シリアライズ/デシリアライズAPIの実装はそれほど難しくありません。
実装方法を知りたい場合は、YAMLの実装をご覧ください。
Types
以下はサポートされている型のリストです。各型の簡単な例は脚注にあります。
- プリミティブ(
int
,float
,str
,bool
)1 - コンテナ
list
,set
,tuple
,dict
2frozenset
3defaultdict
4
typing.Optional
5typing.Union
6 7 8@dataclass
を用いたユーザ定義クラス 9 10typing.NewType
を用いたプリミティブ型 11typing.Any
12typing.Literal
13typing.Generic
14typing.ClassVar
15typing.InitVar
16Enum
およびIntEnum
17- 標準ライブラリ
- PyPIライブラリ
numpy
データ型 23- 宣言的データクラスマッピングを用いた
SQLAlchemy
(実験的)24
このように複雑なクラスを記述することができます。
@serde
class bar:
i: int
@serde
class Foo:
a: int
b: list[str]
c: tuple[int, float, str, bool]
d: dict[str, list[tuple[str, int]]]
e: str | None
f: Bar
Numpy
上記のすべての(デ)シリアライズ方法は、numpy
追加パッケージを使用することで、ほとんどのnumpyデータ型を透過的に扱うことができます。
import numpy as np
import numpy.typing as npt
@serde
class NPFoo:
i: np.int32
j: np.int64
f: np.float32
g: np.float64
h: np.bool_
u: np.ndarray
v: npt.NDArray
w: npt.NDArray[np.int32]
x: npt.NDArray[np.int64]
y: npt.NDArray[np.float32]
z: npt.NDArray[np.float64]
npfoo = NPFoo(
np.int32(1),
np.int64(2),
np.float32(3.0),
np.float64(4.0),
np.bool_(False),
np.array([1, 2]),
np.array([3, 4]),
np.array([np.int32(i) for i in [1, 2]]),
np.array([np.int64(i) for i in [3, 4]]),
np.array([np.float32(i) for i in [5.0, 6.0]]),
np.array([np.float64(i) for i in [7.0, 8.0]]),
)
>>> from serde.json import to_json, from_json
>>> to_json(npfoo)
'{"i": 1, "j": 2, "f": 3.0, "g": 4.0, "h": false, "u": [1, 2], "v": [3, 4], "w": [1, 2], "x": [3, 4], "y": [5.0, 6.0], "z": [7.0, 8.0]}'
>>> from_json(NPFoo, to_json(npfoo))
NPFoo(i=1, j=2, f=3.0, g=4.0, h=False, u=array([1, 2]), v=array([3, 4]), w=array([1, 2], dtype=int32), x=array([3, 4]), y=array([5., 6.], dtype=float32), z=array([7., 8.]))
SQLAlchemy宣言的データクラスマッピング(実験的)
SQLAlchemy宣言的データクラスマッピング統合の実験的サポートが追加されましたが、@serde(type_check=strict)
やserde.field()
などの特定の機能は現在サポートされていません。
実験的な機能に依存する場合、本番環境での使用には注意が必要です。
コードを徹底的にテストし、潜在的な制限や予期せぬ動作に注意することを推奨します。
新しい型のサポートが必要ですか?
標準ライブラリに現在サポートされていない型を使用する必要がある場合は、Githubでissueを作成してリクエストしてください。
なお、サードパーティのPythonパッケージの型については、numpyのような人気のあるパッケージの型を除き、pyserdeをできるだけシンプルに保つためにサポートする予定はありません。
カスタムクラスやフィールドシリアライザの使用を推奨します。
examples/any.py を参照
Decorators
@serde
@serde
は @serialize
と @deserialize
デコレータのラッパーです。
以下のコードがあるとします。
@serde
class Foo:
...
上記のコードは、以下のコードと同等です。
@deserialize
@serialize
@dataclass
class Foo:
...
@serde
デコレータは以下を行います。
- クラスに
@serialize
と@deserialize
デコレータを追加します。 - クラスが
@dataclass
を持っていない場合、@dataclass
デコレータを追加します。 - 両方の(デ)シリアライズ属性をデコレータに渡すことができます。
serializer
属性は@deserialize
で無視され、deserializer
属性は@serialize
で無視されます。
@serde(serializer=serializer, deserializer=deserializer)
@dataclass
class Foo:
...
注記:
@serde
は@dataclass
デコレータなしで動作します。
これはserdeが@dataclass
を自動的に検出し、宣言されたクラスに追加するからです。
しかし@dataclass
を定義しない場合、mypy ではToo many arguments
またはUnexpected keyword argument
というエラーが発生します。これは mypy の制限によるものです。@serde class Foo: ...
しかし、PEP681に準拠した型チェッカー(例:pyright)を使用すると、pyserdeが PEP681 dataclass_transform をサポートしているため、型エラーは発生しません。
@serialize
と @deserialize
@serialize
および @deserialize
は内部的に @serde
によって使用されます。
以下に該当する場合、これら2つのデコレータを使用することを推奨します。
- シリアライズまたはデシリアライズの機能のみが必要な場合
- 以下のように異なるクラス属性を持つことを望む場合
@deserialize(rename_all = "snakecase")
@serialize(rename_all = "camelcase")
class Foo:
...
上記に当てはまらない場合は @serde
の使用を推奨します。
@serde
なしでクラスを(デ)シリアライズする
pyserdeは v0.10.0 以降、@serde
なしでデータクラスを(デ)シリアライズできます。
この機能は、外部ライブラリで宣言されたクラスを使用したい場合や、@serde
デコレータが型チェッカーで機能しない場合に便利です。この例を参照してください。
どのように動作するのでしょうか?
それは非常に単純で、クラスが@serde
デコレータを持っていない場合、pyserdeは @serde
デコレータを追加します。
最初のAPI呼び出しには時間がかかるかもしれませんが、生成されたコードは内部的にキャッシュされるため問題にはなりません。
以下にサードパーティパッケージのクラスをデシリアライズする例を示します。
@dataclass
class External:
...
to_dict(External()) # "@serde" なしで動作します
この場合、rename_all
などのクラス属性を指定することはできません。
外部のデータクラスにクラス属性を追加したい場合は、データクラスを拡張することでそれを行う方法があります。この例を参照してください。
@dataclass
class External:
some_value: int
@serde(rename_all="kebabcase")
@dataclass
class Wrapper(External):
pass
前方参照をどのように使用するか?
pyserdeは前方参照をサポートしています。
ネストされたクラス名を文字列で置き換えると、pyserdeはネストされたクラスが定義された後にデコレータを探して評価します。
from __future__ import annotations # annotationsをインポート
@dataclass
class Foo:
i: int
s: str
bar: Bar # "Bar" は後で宣言されていても指定できます
@serde
@dataclass
class Bar:
f: float
b: bool
# "Bar" が定義された後に pyserde デコレータを評価します
serde(Foo)
PEP563 Annotationsによる遅延評価
pyserdeは PEP563 Annotationsによる遅延評価をサポートしています。
from __future__ import annotations
from serde import serde
@serde
class Foo:
i: int
s: str
f: float
b: bool
def foo(self, cls: Foo): # 定義される前に "Foo" 型を使用できます
print('foo')
完全な例については examples/lazy_type_evaluation.py を参照してください。
Class Attributes
クラス属性は、クラスの(デ)シリアライズの動作をカスタマイズするために serialize
/ deserialize
デコレータの引数として指定できます。
フィールドをカスタマイズしたい場合は、フィールド属性の使用を検討してください。
dataclassesの属性
frozen
dataclassの frozen
クラス属性は期待通りに機能します。
kw_only
バージョン0.12.2で新規追加。dataclassの kw_only
クラス属性は期待通りに機能します。
@serde
@dataclass(kw_only=True)
class Foo:
i: int
s: str
f: float
b: bool
完全な例については、examples/kw_only.pyを参照してください。
pyserdeの属性
rename_all
rename_all
を使うと、フィールド名を指定されたケーススタイルに変換できます。
以下の例では、キャメルケースのフィールド名をスネークケースに変換しています。
ケーススタイルの変換処理は python-casefy パッケージに依存しています。
サポートされるケーススタイルの一覧は python-casefy のドキュメントを参照してください。
@serde(rename_all = 'camelcase')
class Foo:
int_field: int
str_field: str
f = Foo(int_field=10, str_field='foo')
print(to_json(f))
上記のコードを実行すると、int_field
は intField
に、str_field
は strField
に変換されます。
{"intField": 10, "strField": "foo"}
注記:
rename_all
クラス属性とrename
フィールド属性が同時に使用される場合、rename
が優先されます。@serde(rename_all = 'camelcase') class Foo: int_field: int str_field: str = field(rename='str-field') f = Foo(int_field=10, str_field='foo') print(to_json(f))
上記のコードは以下を出力します。
{"intField": 10, "str-field": "foo"}
完全な例については、examples/rename_all.pyを参照してください。
tagging
バージョン0.7.0で新規追加。詳細は Union を参照してください。
class_serializer
と class_deserializer
バージョン0.13.0で新規追加。
クラスレベルでカスタム(デ)シリアライザを使用したい場合、class_serializer
および class_deserializer
属性に(デ)シリアライザオブジェクトを渡すことができます。
カスタム(デ)シリアライザは、C++のように複数のメソッドのオーバーロードを可能にするPythonライブラリ plum に依存しています。
plumを使用すると、堅牢なカスタム(デ)シリアライザを簡単に書くことができます。
class MySerializer:
@dispatch
def serialize(self, value: datetime) -> str:
return value.strftime("%d/%m/%y")
class MyDeserializer:
@dispatch
def deserialize(self, cls: Type[datetime], value:
Any) -> datetime:
return datetime.strptime(value, "%d/%m/%y")
@serde(class_serializer=MySerializer(), class_deserializer=MyDeserializer())
class Foo:
v: datetime
旧来の serializer
と deserializer
との大きな違いは、 新しいclass_serializer
と class_deserializer
が pyserde のコード生成レベルでより深く統合されていることです。
これにより Optional や List、ネストされた dataclass を自分で処理する必要はありません。
カスタムクラスの(デ)シリアライザはすべての(デ)シリアライズのレベル(単純なデータ型から複雑なネストされたデータ構造まで、あらゆる種類の(デ)シリアライズ処理)で使用されるため、サードパーティ製の型もビルトイン型のように扱うことが可能です。
また、
- フィールドシリアライザとクラスシリアライザの両方が指定されている場合、フィールドシリアライザが優先されます。
- 旧と新のクラスシリアライザの両方が指定されている場合、新しいクラスシリアライザが優先されます。
💡 ヒント:複数の
serialize
メソッドを実装する場合、型チェッカーから、Redefinition of unused serialize
という警告が出ることがあります。
その場合は、plum.overload
とplum.dispatch
を使用して回避してください。
詳細は plumのドキュメント を参照してください。from plum import dispatch, overload class Serializer: # @overload を使用 @overload def serialize(self, value: int) -> Any: return str(value) # @overload を使用 @overload def serialize(self, value: float) -> Any: return int(value) # メソッド追加時は必ず @dispatch を追加してください。Plumが型チェッカーからの警告を消してくれます @dispatch def serialize(self, value: Any) -> Any: ...
完全な例については、examples/custom_class_serializer.pyを参照してください。
serializer
と deserializer
注記:
serializer
** と **deserializer
は、バージョン0.13.0以降、非推奨となりました。
class_serializer
およびclass_deserializer
の使用を検討してください。
クラスレベルでカスタムの(デ)シリアライザを使用したい場合、serializer
および deserializer
属性に(デ)シリアライザメソッドを渡すことができます。
def serializer(cls, o):
if cls is datetime:
return o.strftime('%d/%m/%y')
else:
raise SerdeSkip()
def deserializer(cls, o):
if cls is datetime:
return datetime.strptime(o, '%d/%m/%y')
else:
raise SerdeSkip()
@serde(serializer=serializer, deserializer=deserializer)
class Foo:
a: datetime
完全な例については、examples/custom_legacy_class_serializer.py を参照してください。
type_check
バージョン0.9.0で新規追加。詳細は Type Check を参照してください。
serialize_class_var
バージョン0.9.8で新規追加。
dataclasses.fields
はクラス変数を含まないため1、pyserdeはデフォルトでクラス変数をシリアライズしません。
serialize_class_var
を使用することで typing.ClassVar
のフィールドをシリアライズすることができます。
@serde(serialize_class_var=True)
class Foo:
a: ClassVar[int] = 10
完全な例については、examples/class_var.pyを参照してください。
deny_unknown_fields
バージョン0.22.0で新規追加。 pyserdeデコレータのdeny_unknown_fields
オプションはデシリアライズ時のより厳格なフィールドチェックを制御できます。このオプションをTrueにするとデシリアライズ時に宣言されていないフィールドが見つかるとSerdeError
を投げることができます。
以下の例を考えてください。
@serde(deny_unknown_fields=True)
class Foo:
a: int
b: str
deny_unknown_fields=True
が指定されていると、 宣言されているフィールド(この場合aとb)以外がインプットにあると例外を投げます。例えば、
from_json(Foo, '{"a": 10, "b": "foo", "c": 100.0, "d": true}')
上記のコードはフィールドcとdという宣言されていないフィールドがあるためエラーとなります。
完全な例については、examples/deny_unknown_fields.pyを参照してください。
Field Attributes
フィールド属性は、データクラスのフィールドの(デ)シリアライズ動作をカスタマイズするためのオプションです。
dataclassesによって提供される属性
default
と default_factory
dataclassesの default
と default_factory
は期待通りに動作します。
フィールドが default
または default_factory
属性を持つ場合、そのフィールドはオプショナルフィールドのように振る舞います。
フィールドがデータ内に存在する場合、当該フィールドの値がデシリアライズされたオブジェクトに設定されます。
フィールドがデータ内に存在しない場合、指定されたデフォルト値がデシリアライズされたオブジェクトに設定されます。
class Foo:
a: int = 10
b: int = field(default=10) # field "a"と同じ
c: dict[str, int] = field(default_factory=dict)
print(from_dict(Foo, {})) # Foo(a=10, b=10, c={}) を出力
完全な例については、examples/default.pyを参照してください。
ClassVar
dataclasses.ClassVar
はデータクラスのクラス変数であり、dataclassはClassVarを擬似的なフィールドとして扱います。
dataclasses.field
はクラス変数を含まないため、pyserdeはデフォルトの動作として ClassVar
フィールドを(デ)シリアライズしません。
ClassVarフィールドをシリアライズする場合は、serialize_class_varクラス属性の使用を検討してください。
完全な例については、examples/class_var.pyを参照してください。
pyserdeによって提供される属性
フィールド属性は serde.field
または dataclasses.field
を通じて指定することができます。
型チェックが機能するため、 serde.field
の使用を推奨します。
以下は、rename
属性を serde.field
と dataclasses.field
の両方で指定する例です。
@serde.serde
class Foo:
a: str = serde.field(rename="A")
b: str = dataclasses.field(metadata={"serde_rename"="B"})
rename
rename
は(デ)シリアライズ中にフィールド名を変更するために使用されます。
この属性は、フィールド名にPythonのキーワード(予約語)を使用したい場合に便利です。
以下のコードはフィールド名 id
を ID
に変更する例です。
@serde
class Foo:
id: int = field(rename="ID")
完全な例については、examples/rename.pyを参照してください。
skip
skip
はこの属性を持つフィールドの(デ)シリアライズをスキップします。
@serde
class Resource:
name: str
hash: str
metadata: dict[str, str] = field(default_factory=dict, skip=True)
完全な例については、examples/skip.pyを参照してください。
skip_if
skip_if
は条件関数が True
を返す場合にフィールドの(デ)シリアライズをスキップします。
@serde
class World:
buddy: str = field(default='', skip_if=lambda v: v == 'Pikachu')
完全な例については、examples/skip.pyを参照してください。
skip_if_false
skip_if_false
はフィールドが False
と評価される場合にフィールドの(デ)シリアライズをスキップします。
例えば、以下のコードは enemies
が空であれば(デ)シリアライズをスキップします。
@serde
class World:
enemies: list[str] = field(default_factory=list, skip_if_false=True)
完全な例については、examples/skip.pyを参照してください。
skip_if_default
skip_if_default
はフィールドがデフォルト値とイコールである場合にフィールドの(デ)シリアライズをスキップします。
例えば、以下のコードは town
が Masara Town
であれば(デ)シリアライズをスキップします。
@serde
class World:
town: str = field(default='Masara Town', skip_if_default=True)
完全な例については、examples/skip.pyを参照してください。
alias
フィールド名に別名を設定できます。エイリアスはデシリアライズ時にのみ機能します。
@serde
class Foo:
a: str = field(alias=["b", "c"])
上記の例では、 Foo
は {"a": "..."}
, {"b": "..."}
、または {"c": "..."}
のいずれかからデシリアライズできます。
完全な例については、examples/alias.pyを参照してください。
serializer
と deserializer
特定のフィールドに対してカスタム(デ)シリアライザを使用したい場合があります。
例えば、以下のようなケースです。
- datetimeを異なる形式でシリアライズしたい。
- サードパーティパッケージの型をシリアライズしたい。
以下の例では、フィールド a
はデフォルトのシリアライザで "2021-01-01T00:00:00"
にシリアライズされますが、フィールド b
はカスタムシリアライザで "01/01/21"
にシリアライズされます。
@serde
class Foo:
a: datetime
b: datetime = field(serializer=lambda x: x.strftime('%d/%m/%y'), deserializer=lambda x: datetime.strptime(x, '%d/%m/%y'))
完全な例については、examples/custom_field_serializer.pyを参照してください。
flatten
ネストされた構造のフィールドをフラットにできます。
@serde
class Bar:
c: float
d: bool
@serde
class Foo:
a: int
b: str
bar: Bar = field(flatten=True)
上記の例では、Bar
の c
および d
フィールドが Foo
で定義されているかのようにデシリアライズされます。
Foo
をJSON形式にシリアライズすると {"a":10,"b":"foo","c":100.0,"d":true}
を取得します。
完全な例については、examples/flatten.pyを参照してください。
Union Representation
pyserde>=0.7
では、Union が(デ)シリアライズされる方法を制御するための属性が提供されています。この概念は serde-rs にあるものと同じです。
これらの表現は dataclass にのみ適用され、dataclassではないオブジェクトは常に Untagged
で(デ)シリアライズされます。
Untagged
以下は、 pyserde<0.7
におけるデフォルトの Union 表現です。dataclass が与えられた場合を例に上げます。
@serde
class Bar:
b: int
@serde
class Baz:
b: int
@serde(tagging=Untagged)
class Foo:
a: Union[Bar, Baz]
注意点として、Bar
と Baz
は同じフィールド名と型を持っています。
Foo(Baz(10))
を辞書にシリアライズすると、{"a": {"b": 10}}
が得られます。
しかし、{"a": {"b": 10}}
をデシリアライズすると、 Foo(Baz(10))
ではなく Foo(Bar(10))
が得られます。
これは、pyserde が Untagged
を使った Union の dataclass を正しく(デ)シリアライズできないことを意味します。
このため、pyserde は他の Union 表現オプションを提供しています。
ExternalTagging
これは 0.7 以降のデフォルトの Union 表現です。ExternalTagging
を使用したクラス宣言は以下のようになります。
@serde(tagging=ExternalTagging)
class Foo:
a: Union[Bar, Baz]
Foo(Baz(10))
を辞書にシリアライズすると、 {"a": {"Baz": {"b": 10}}}
が得られ、デシリアライズすると Foo(Baz(10))
になります。
注意: dataclass でないオブジェクトは、
tagging
属性に関わらず常にUntagged
で(デ)シリアライズされます。
これはタグに使用できる情報がないためです。Untagged
の欠点は、特定のタイプを正しく非シリアライズできないことです。例えば、以下のクラスの
Foo({1, 2, 3})
は{"a": [1, 2, 3]}
にシリアライズされますが、デシリアライズするとFoo([1, 2, 3])
になります。@serde(tagging=ExternalTagging) class Foo: a: Union[list[int], set[int]]
InternalTagging
InternalTagging
を使用したクラス宣言は以下のようになります。
@serde(tagging=InternalTagging("type"))
class Foo:
a: Union[Bar, Baz]
Foo(Baz(10))
を辞書にシリアライズすると、{"a": {"type": "Baz", "b": 10}}
が得られ、それを Foo(Baz(10))
にデシリアライズできます。
type
タグは Baz
の辞書内にエンコードされます。
AdjacentTagging
AdjacentTagging
を使用したクラス宣言は以下のようになります。
@serde(tagging=AdjacentTagging("type", "content"))
class Foo:
a: Union[Bar, Baz]
Foo(Baz(10))
を辞書にシリアライズすると、{"a": {"type": "Baz", "content": {"b": 10}}}
が得られ、それを Foo(Baz(10))
にデシリアライズできます。type
タグは Baz
の辞書内にエンコードされ、Baz
のフィールドは content
内にエンコードされます。
Union 型の直接的な(デ)シリアライズ
v0.12.0 で新しく追加されました。
v0.12 以前において、(デ)シリアライズ API(例: to_json
, from_json
)に直接 Union 型のデータを渡すことは部分的にサポートされていましたが、Union 型は常にタグ無しとして扱われました。
これは、利用者側で Union タグを変更することができなかったことを意味します。
以下の例は、タグ無しのため Bar
に正しくデシリアライズできません。
@serde
class Foo:
a: int
@serde
class Bar:
a: int
bar = Bar(10)
s = to_json(bar)
print(s)
# {"a": 10} を出力
print(from_json(Union[Foo, Bar], s))
# Foo(10) を出力
v0.12.0 以降、pyserde は(デ)シリアライズ API で渡された Union 型を適切に扱えます。
Union 型は、pyserde のデフォルトのタグ付けである外部タグ付けとして扱われます。
以下の例は Bar
として正しく(デ)シリアライズできます。
@serde
class Foo:
a: int
@serde
class Bar:
a: int
bar = Bar(10)
s = to_json(bar, cls=Union[Foo, Bar])
print(s)
# {"Bar" {"a": 10}} を出力
print(from_json(Union[Foo, Bar], s))
# Bar(10) を出力
また、serde.InternalTagging
、serde.AdjacentTagging
、および serde.Untagged
を使用してタグ付けを変更できます。
それでは、上記の例を用いてタグ付けを変更してみましょう。
タグ付けを変更するには、 to_json
に新しい引数 cls
を渡す必要があります。
また、Unionクラスは InternalTagging
、AdjacentTagging
、または Untagged
で必要なパラメータと共にラップされる必要があります。
-
InternalTagging
from serde import InternalTagging s = to_json(bar, cls=InternalTagging("type", Union[Foo, Bar])) print(s) # {"type": "Bar", "a": 10} を出力 print(from_json(InternalTagging("type", Union[Foo, Bar]), s)) # Bar(10) を出力
-
AdjacentTagging
from serde s = to_json(bar, cls=AdjacentTagging("type", "content", Union[Foo, Bar])) print(s) # {"type": "Bar", "content": {"a": 10}} を出力 print(from_json(AdjacentTagging("type", "content", Union[Foo, Bar]), s)) # Bar(10) を出力
-
Untagged
from serde import Untagged s = to_json(bar, cls=Untagged(Union[Foo, Bar])) print(s) # {"a": 10} を出力 print(from_json(Untagged(Union[Foo, Bar]), s)) # Foo(10) を出力
Type Checking
pyserdeはv0.9からランタイム型チェックを提供しています。
v0.14で完全に作り直され、beartypeを使用してより洗練され信頼性の高いものとなりました。
型安全で堅牢なプログラムを書くためには、型チェックを常に有効にすることを強く推奨します。
strict
厳格な型チェック strict
は、(デ)シリアライズとオブジェクト構築の際にすべてのフィールド値を宣言された型と照合します。
これはv0.14以降のデフォルトの型チェックモードです。
このモードでは、クラス属性を指定せずに @serde
デコレータを使用してクラスを宣言した場合、@serde(type_check=strict)
と見なされ、厳格な型チェックが有効になります。
@serde
class Foo:
s: str
例えば、以下のように間違った型のオブジェクトでFoo
を呼び出すと、
foo = Foo(10)
以下のエラーが発生します。
beartype.roar.BeartypeCallHintParamViolation: Method __main__.Foo.__init__() parameter s=10 violates type hint <class 'str'>, as int 10 not instance of str.
注記: 2024年2月時点でbeartypeは検証フックを提供していないため、コンストラクターからはSerdeErrorではなくbeartypeの例外が発生します。
同様に、間違った型のオブジェクトで(デ)シリアライズAPIを呼び出すと、
print(to_json(foo))
再びエラーが発生します。
serde.compat.SerdeError: Method __main__.Foo.__init__() parameter s=10 violates type hint <class 'str'>, as int 10 not instance of str.
注記: beartypeによる型チェックにはいくつかの注意点があります。
- beartypeは変更されたプロパティを検証できません。
以下のコードでは、プロパティ
s
が最後に変更されていますが、beartypeはこのケースを検出できません。@serde class Foo: s: str f = Foo("foo") f.s = 100
- beartypeはコンテナ内の各要素を検証することはできません。これはバグではなく、beartypeの設計原則です。Does beartype actually do anything?を参照してください。
coerce
型強制 coerce
は、(デ)シリアライズ中に値を宣言された型に自動的に変換します。
@serde(type_check=Coerce)
class Foo:
s: str
foo = Foo(10)
# pyserdeは自動的に int 値の 10 を str の "10" に変換します
# {"s": "10"}が出力されます
print(to_json(foo))
しかし、値が宣言された型に変換できない場合(例えば、値が foo
で型が int
の場合)、pyserde はSerdeError
を発生させます。
disabled
これはpyserde v0.8.3およびv0.9.xまでのデフォルトの挙動です。
型強制またはチェックは実行されません。
利用者が間違った値を入力しても、pyserdeは型の不整合を無視して処理を続行します。
@serde
class Foo:
s: str
foo = Foo(10)
# pyserdeは型の整合性を確認しないため、{"s": 10} が出力されます
print(to_json(foo))
Extending pyserde
pyserde はビルトイン型以外をサポートするために pyserde を拡張する 3 つの方法を提供しています。
カスタムフィールド(デ)シリアライザ
詳細はカスタムフィールドシリアライザを参照してください。
💡 ヒント:
serde.field
を独自のフィールド関数でラップすると以下のようになります。import serde def field(*args, **kwargs): serde.field(*args, **kwargs, serializer=str) @serde class Foo: a: int = field(default=0) # フィールドシリアライザの設定
カスタムクラス(デ)シリアライザ
詳細はカスタムクラスシリアライザを参照してください。
カスタムグローバル(デ)シリアライザ
add_serializer
と add_deserializer
を使ってクラス(デ)シリアライザを登録することで、コードベース全体でカスタム(デ)シリアライザを適用できます。
登録されたクラス(デ)シリアライザは pyserde のグローバル空間にスタックされ、すべての pyserde クラスで自動的に使用されます。
例:isodateパッケージを使用して datetime.timedelta
のカスタム(デ)シリアライザを実装する。
以下は、datetime.timedelta
のためのクラス(デ)シリアライザを登録するコードです。
このパッケージは実際に PyPI で pyserde-timedelta として公開されています。
from datetime import timedelta
from plum import dispatch
from typing import Type, Any
import isodate
import serde
class Serializer:
@dispatch
def serialize(self, value: timedelta) -> Any:
return isodate.duration_isoformat(value)
class Deserializer:
@dispatch
def deserialize(self, cls: Type[timedelta], value: Any) -> timedelta:
return isodate.parse_duration(value)
def init() -> None:
serde.add_serializer(Serializer())
serde.add_deserializer(Deserializer())
このパッケージの利用者は、 serde_timedelta.init()
を呼び出すだけで datetime.timedelta
のカスタム(デ)シリアライザの機能を再利用できます。
import serde_timedelta
from serde import serde
from serde.json import to_json, from_json
from datetime import timedelta
serde_timedelta.init()
@serde
class Foo:
a: timedelta
f = Foo(timedelta(hours=10))
json = to_json(f)
print(json)
print(from_json(Foo, json))
以下のように datetime.timedelta
がISO 8601 形式でシリアライズされます!
{"a":"PT10H"}
Foo(a=datetime.timedelta(seconds=36000))
💡 ヒント:クラス(デ)シリアライザはいくつでも登録できます。これは、好きなだけpyserde拡張を使用できることを意味します。 登録された(デ)シリアライザはメモリにスタックされます。
なお、1つの(デ)シリアライザは他の(デ)シリアライザによってオーバーライドされる可能性があります。
例:次の順序で3つのカスタムシリアライザを登録すると、最初のシリアライザは3番目によって完全に上書きされます。2番目は異なる型用に実装されているため機能します。
int
用のシリアライザを登録float
用のシリアライザを登録int
用のシリアライザを登録
こちらは、v0.13.0 で新しく追加されました。
FAQ
pyserdeによって生成されたコードをどのように確認できますか?
pyserdeはコマンドラインとして動作する inspect
サブモジュールを提供しています。
python -m serde.inspect <PATH_TO_FILE> <CLASS>
例:pyserdeプロジェクト内で以下を実行します。
cd pyserde
poetry shell
python -m serde.inspect examples/simple.py Foo
出力
Loading simple.Foo from examples.
==================================================
Foo
==================================================
--------------------------------------------------
Functions generated by pyserde
--------------------------------------------------
def to_iter(obj, reuse_instances=True, convert_sets=False):
if reuse_instances is Ellipsis:
reuse_instances = True
if convert_sets is Ellipsis:
convert_sets = False
if not is_dataclass(obj):
return copy.deepcopy(obj)
Foo = serde_scope.types["Foo"]
res = []
res.append(obj.i)
res.append(obj.s)
res.append(obj.f)
res.append(obj.b)
return tuple(res)
...