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型が必要な場合に使用します。

試してみる — JSON to Python Converter

フルツールを開く