始めに
結果が分かれば単純なミスでした。Pydanticで型定義しただけと考えていましたが、少しでも振る舞いが変わるときはちゃんと確認しましょう。
環境
- Python
- 3.13
- Pydantic
- 2.11.4
実装
変更前のコード
SaaS企業が外部連携しやすいようにライブラリを公開していることがあります。基本的にはライブラリ内で型定義はすでに行われている状態ですが、SaaS側でユーザが自由にプロパティを追加・修正できる項目がある場合には、該当プロパティを参照及び更新するためには自前でカスタマイズして連携する必要があります。今回発生したケースでもその自前カスタマイズをする必要があったのですが、コード上で特に型定義もせずに直接dictで設定していました。
from typing import Any from pydantic import BaseModel class Update(BaseModel): id: int data: Any record = Update( id=1, data={ "id": "aaa", "value": "ccc", "pattern": 1, "now": "2025-05-16T17:32:30.204923+09:00" } ) # record インスタンスをもとに 外部ライブラリに連携する処理
変更後のコード
型がないと横展開時の漏れに気づけなかったり、必要なパラメータが足りているのか判断が難しくなるため、今後の拡張性を見越して型定義をしました。リファクタリングとしてpydantic
で型定義しつつ、 model_dump
を使用することでdictで渡せるように変更しました。
from typing import Any from datetime import datetime from enum import IntEnum from pydantic import BaseModel class PatternEnum(IntEnum): A = 0 B = 1 class CustomModel(BaseModel): id: str value: str pattern: PatternEnum now: datetime class Update(BaseModel): id: int data: CustomModel record = Update( id=1, data=CustomModel( id="aaa", value="ccc", pattern=1, now=datetime.now() ).model_dump() ) # record インスタンスをもとに 外部ライブラリに連携する処理
ただし、これではうまく動きませんでした。原因としては、SaaSと連携するために最終的にシリアライズしてデータ連携する必要があるのですが、datetime
をシリアライズしたタイミングで次のエラーが出るからです。
*** TypeError: Object of type datetime is not JSON serializable
対策としてmode='json'
を付与することで、シリアライズも行えるようになっています。
{'id': 'aaa', 'value': 'ccc', 'pattern': 1, 'now': '2025-05-16T17:44:50.670465+09:00'}
また、ライブラリ側の型ヒントがdictではなく、strでも問題ない場合はmodel_dump_json
を使用するといいでしょう。
'{"id":"aaa","value":"ccc","pattern":1,"now":"2025-05-16T17:44:50.670465+09:00"}'
ソースコード
実際のテストコードで問題と解決策を確認できます: - GitHub: test_to_json.py - Pydanticモデルのシリアライズテスト
まとめ
外部ライブラリに連携する際には、model_dump
のmode=json
を使用すると安心。 または、文字列でよければmodel_dump_json
を使用する。
終わりに
リファクタリングと言いつつ、str
とdatetime
を型変換している箇所が悪いんですけどね。元々、一旦DBに格納した情報をSaaS側に反映する用途で使用していたため、DBで使用していた型をそのまま流用してしまっていました。流用すること自体は問題ないのですが、差分を理解しないまま流用してはいけませんね。
特に、Pydanticで型定義を導入することは開発効率とコードの品質向上に大きく貢献しますが、型変換の過程でデータがどのように変化するかを理解しておくことが重要です。今回はシンプルなケースでしたが、これを機にPydanticのmodel_dump
やmodel_dump_json
の挙動についても理解を深めることができました。単純ですが勉強になりました。