Introduction

pyserdedataclassesベースのシンプルで強力なシリアライゼーションライブラリで、クラスを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以外のデータ形式を扱う場合は、追加の依存関係をインストールする必要があります。
適切なデータ形式で動作するには、msgpacktomlyaml の追加パッケージをインストールしてください。
使用しない場合はスキップして構いません。
例えば、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

利用可能な追加パッケージ

  • allmsgpacktomlyamlnumpyorjsonsqlalchemy をインストール
  • msgpackmsgpack をインストール
  • tomltomlitomli-w をインストール
    • 注記:python 3.11以降は tomllib を使用
  • yamlpyyaml をインストール
  • numpynumpy をインストール
  • orjsonorjson をインストール
  • sqlalchemysqlalchemy をインストール

最初の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_jsonFooクラスと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は、dicttupleJSONYAMLTOMLMsgPackPickle などのさまざまなデータ形式をシリアライズおよびデシリアライズできます。
各APIは追加のキーワード引数を取ることができ、これらの引数はpyserdeで使用されるベースパッケージへと渡されます。

例えば、YAMLでフィールドの順序を保持したい場合、serde.yaml.to_yamlsort_key=Trueを渡すことができます。

serde.yaml.to_yaml(foo, sort_key=True)

sort_key=Trueyaml.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

以下はサポートされている型のリストです。各型の簡単な例は脚注にあります。

このように複雑なクラスを記述することができます。

@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をできるだけシンプルに保つためにサポートする予定はありません。
カスタムクラスやフィールドシリアライザの使用を推奨します。

1

examples/simple.py を参照

5

examples/union.py を参照

6

examples/union.py を参照

9

examples/simple.py を参照

10

examples/nested.py を参照

11

examples/newtype.py を参照

12

examples/any.py を参照

13

examples/literal.py を参照

14

examples/generics.py を参照

15

examples/class_var.py を参照

16

examples/init_var.py を参照

17

examples/enum34.py を参照

20

examples/type_uuid.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_fieldintField に、str_fieldstrField に変換されます。

{"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_serializerclass_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

旧来の serializerdeserializer との大きな違いは、 新しいclass_serializerclass_deserializer が pyserde のコード生成レベルでより深く統合されていることです。
これにより Optional や List、ネストされた dataclass を自分で処理する必要はありません。

カスタムクラスの(デ)シリアライザはすべての(デ)シリアライズのレベル(単純なデータ型から複雑なネストされたデータ構造まで、あらゆる種類の(デ)シリアライズ処理)で使用されるため、サードパーティ製の型もビルトイン型のように扱うことが可能です。

また、

  • フィールドシリアライザとクラスシリアライザの両方が指定されている場合、フィールドシリアライザが優先されます。
  • 旧と新のクラスシリアライザの両方が指定されている場合、新しいクラスシリアライザが優先されます。

💡 ヒント:複数の serialize メソッドを実装する場合、型チェッカーから、Redefinition of unused serializeという警告が出ることがあります。
その場合は、plum.overloadplum.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を参照してください。

serializerdeserializer

注記: 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 を参照してください。

Field Attributes

フィールド属性は、データクラスのフィールドの(デ)シリアライズ動作をカスタマイズするためのオプションです。

dataclassesによって提供される属性

defaultdefault_factory

dataclassesの defaultdefault_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.fielddataclasses.field の両方で指定する例です。

@serde.serde
class Foo:
    a: str = serde.field(rename="A")
    b: str = dataclasses.field(metadata={"serde_rename"="B"})

rename

rename は(デ)シリアライズ中にフィールド名を変更するために使用されます。
この属性は、フィールド名にPythonのキーワード(予約語)を使用したい場合に便利です。

以下のコードはフィールド名 idID に変更する例です。

@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 はフィールドがデフォルト値とイコールである場合にフィールドの(デ)シリアライズをスキップします。

例えば、以下のコードは townMasara 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を参照してください。

serializerdeserializer

特定のフィールドに対してカスタム(デ)シリアライザを使用したい場合があります。

例えば、以下のようなケースです。

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

上記の例では、Barc および 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]

注意点として、BarBaz は同じフィールド名と型を持っています。
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.InternalTaggingserde.AdjacentTagging、および serde.Untagged を使用してタグ付けを変更できます。

それでは、上記の例を用いてタグ付けを変更してみましょう。

タグ付けを変更するには、 to_json に新しい引数 cls を渡す必要があります。
また、Unionクラスは InternalTaggingAdjacentTagging、または 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による型チェックにはいくつかの注意点があります。

  1. beartypeは変更されたプロパティを検証できません。

以下のコードでは、プロパティ s が最後に変更されていますが、beartypeはこのケースを検出できません。

@serde
class Foo:
    s: str

f = Foo("foo")
f.s = 100
  1. 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_serializeradd_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番目は異なる型用に実装されているため機能します。

  1. int 用のシリアライザを登録
  2. float 用のシリアライザを登録
  3. 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)
...