Python Union型による混合型JSONフィールドの処理
レスポンス間で異なる型を持つJSONフィールドをPython Union型に変換する方法を学びます。Union[X, Y]、X | Y構文、型の絞り込みを解説します。
Advanced Patterns
詳細な説明
混合JSONフィールドのためのUnion型
一部のJSON APIは、コンテキストに応じて異なる型のフィールドを返します — あるレスポンスでは文字列、別のレスポンスではオブジェクトになることがあります。Pythonはこれを Union で処理します。
JSONの例(文字列結果)
{
"type": "simple",
"result": "success"
}
JSONの例(オブジェクト結果)
{
"type": "detailed",
"result": {
"status": "success",
"count": 42
}
}
生成されるPython
from dataclasses import dataclass
from typing import Union
@dataclass
class DetailedResult:
status: str
count: int
@dataclass
class Response:
type: str
result: Union[str, DetailedResult]
Python 3.10+の構文
@dataclass
class Response:
type: str
result: str | DetailedResult
型の絞り込み
isinstance() を使ってランタイムでunionを絞り込みます:
if isinstance(response.result, str):
print(response.result.upper()) # mypyはstrであることを認識
elif isinstance(response.result, DetailedResult):
print(response.result.count) # mypyはDetailedResultであることを認識
LiteralによるDiscriminated Unions
よりクリーンな絞り込みのために、判別フィールドを使用します:
from typing import Literal
@dataclass
class SimpleResponse:
type: Literal["simple"]
result: str
@dataclass
class DetailedResponse:
type: Literal["detailed"]
result: DetailedResult
Response = SimpleResponse | DetailedResponse
Pydanticの場合
Pydantic v2はdiscriminated unionsをネイティブにサポートします:
from pydantic import BaseModel, Discriminator, Tag
from typing import Annotated, Union
class SimpleResponse(BaseModel):
type: Literal["simple"]
result: str
class DetailedResponse(BaseModel):
type: Literal["detailed"]
result: DetailedResult
Response = Annotated[
Union[SimpleResponse, DetailedResponse],
Discriminator("type"),
]
Unionを使うべき場面
- 多態的なAPIレスポンス — typeやstatusフィールドに基づく異なる形状。
- Webhookペイロード — イベントタイプがペイロード構造を決定する。
- 柔軟な入力 — 同じフィールドで複数のフォーマットを受け入れる。
ユースケース
ペイロード構造がイベントタイプによって異なるWebhook APIを消費しており、mypyがすべてのバリアントを正しく処理していることを検証できるPython型が必要な場合に使用します。