DevToolBoxFREE
BlogAdvertise

JSONからPythonデータクラス変換:Pydantic、dataclasses、TypedDict、attrsガイド

17分で読了by DevToolBox

JSONからPythonデータクラスへの変換は、モダンなPython開発で最も一般的なタスクの一つです。REST APIからJSON応答を受信する際、データを安全に扱うために適切に構造化されたPythonデータクラス、Pydanticモデル、またはTypedDictが必要です。この包括的なガイドでは、型マッピング、Python JSONパース戦略、Pydantic、attrs、TypedDict、ベストプラクティスをカバーします。

無料オンラインJSON to Pythonデータクラスコンバーターをお試しください。

JSON to Pythonデータクラス変換とは?

JSONはWeb API、設定ファイル、NoSQLデータベースの主要なデータ交換フォーマットです。Pythonは動的型付け言語ですが、データクラス、Pydanticモデル、TypedDictによる明示的な型定義から大きな恩恵を受けます。JSON to Pythonデータクラス変換はJSONドキュメントを分析し、対応するPythonクラスを生成します。

典型的なFastAPIアプリケーションでは、ルートハンドラーがJSON文字列としてHTTPリクエストボディを受信します。フレームワークはPydanticのBaseModelを使用してJSONをPythonオブジェクトに変換する必要があります。JSON to Pythonクラスコンバーターはこれらのデータモデルの作成を自動化します。

Django REST Framework、データパイプライン、JSON設定ファイルを読むCLIツールでも同じ変換が必要です。プロセスは同一です:JSON構造の検査、型の決定、ネストと配列の処理。

JSON to Python:型マッピング

JSONの型がPythonの型にどのようにマッピングされるかを理解することが変換の基盤です:

JSON型Python型備考
string"hello"str常にstr;ISO日付にはdatetime
number(整数)42intPythonでは任意精度
number(小数)3.14floatDecimal金融データにはDecimal
booleantruebool直接対応
nullnullNoneOptional[T]またはT | None
array[1,2]list[T]Python 3.9+でlist[T]
object{"k":"v"}ネストクラス強い型付けクラス推奨

JSONからPythonデータクラスを生成する際、Optional[str]str | Noneの選択はPythonバージョンに依存します。金額には常にDecimalを使用してください。

JSON to Python変換の仕組み

JSON to Pythonデータクラスコンバーターは体系的なプロセスに従います:

  1. JSON構造を解析:Python組み込みのjsonモジュールでパース。
  2. フィールド型を推論:各キーバリューペアのPython型を決定。
  3. クラス名を生成:PascalCaseとsnake_caseに変換。
  4. ネストオブジェクトを処理:各オブジェクトが別クラスを生成。
  5. 配列を処理:要素型を分析。
  6. 型アノテーションを追加:PEP 484/604構文。
  7. ソースコードを出力:フォーマットされたPythonコード。

コード例:Pydantic、dataclasses、TypedDict、attrsでJSON to Python

Pydantic v2:BaseModel、Field、バリデーター

PydanticはPythonエコシステムで最も広く使用されているJSON to Pythonライブラリです。FastAPIはデフォルトで使用します:

# === Sample JSON ===
# {
#   "user_id": 1001,
#   "user_name": "Alice",
#   "email": "alice@example.com",
#   "is_active": true,
#   "balance": 1250.75,
#   "tags": ["admin", "developer"],
#   "address": {
#     "street": "123 Main St",
#     "city": "Springfield",
#     "zip_code": "62704"
#   }
# }

# === Pydantic v2 Models ===
from pydantic import BaseModel, Field, field_validator, ConfigDict
from typing import Optional


class Address(BaseModel):
    street: str
    city: str
    zip_code: str = Field(alias="zipCode")

    model_config = ConfigDict(populate_by_name=True)


class User(BaseModel):
    user_id: int
    user_name: str
    email: str
    is_active: bool = True
    balance: float = 0.0
    tags: list[str] = []
    address: Optional[Address] = None

    @field_validator("email")
    @classmethod
    def validate_email(cls, v: str) -> str:
        if "@" not in v:
            raise ValueError("Invalid email address")
        return v.lower().strip()

    model_config = ConfigDict(populate_by_name=True)


# === Parsing JSON string directly ===
json_string = '{"user_id": 1001, "user_name": "Alice", ...}'
user = User.model_validate_json(json_string)

# === Parsing from dict ===
data = {"user_id": 1001, "user_name": "Alice", "email": "alice@example.com"}
user = User.model_validate(data)

# === Serialization ===
print(user.model_dump())          # dict output
print(user.model_dump_json())     # JSON string output

# === List of models ===
from pydantic import TypeAdapter

users_adapter = TypeAdapter(list[User])
users = users_adapter.validate_json(json_array_string)

dataclasses + json:@dataclass、dataclass_json、dacite

Python組み込みのdataclassesモジュールは軽量な代替手段を提供します:

from dataclasses import dataclass, field
from typing import Optional
import json

# === Standard dataclass ===
@dataclass
class Address:
    street: str
    city: str
    zip_code: str

@dataclass
class User:
    user_id: int
    user_name: str
    email: str
    is_active: bool = True
    balance: float = 0.0
    tags: list[str] = field(default_factory=list)
    address: Optional[Address] = None

    def __post_init__(self):
        # Convert nested dict to Address if needed
        if isinstance(self.address, dict):
            self.address = Address(**self.address)

# === Parse from JSON ===
raw = json.loads(json_string)
user = User(**raw)

# === Using dacite for nested structures ===
import dacite

data = json.loads(json_string)
user = dacite.from_dict(data_class=User, data=data)
# dacite handles nested dicts, Optional, Union automatically

# === Using dataclasses-json ===
from dataclasses_json import dataclass_json, LetterCase, config

@dataclass_json(letter_case=LetterCase.CAMEL)
@dataclass
class Product:
    product_id: int
    product_name: str
    unit_price: float
    in_stock: bool = True

# Direct JSON parsing
product = Product.from_json('{"productId": 42, "productName": "Keyboard", ...}')

# Direct JSON serialization
json_output = product.to_json()

# === Frozen (immutable) dataclass ===
@dataclass(frozen=True, slots=True)
class ImmutableConfig:
    host: str
    port: int
    debug: bool = False

TypedDict:ランタイムオーバーヘッドなしの型ヒント

型安全だがゼロランタイムオーバーヘッドのPython JSONパースが必要な場合、TypedDictが辞書の構造的型付けを提供します:

from typing import TypedDict, NotRequired
import json

# === TypedDict definitions ===
class Address(TypedDict):
    street: str
    city: str
    zip_code: str

class User(TypedDict):
    user_id: int
    user_name: str
    email: str
    is_active: bool
    balance: float
    tags: list[str]
    address: NotRequired[Address]  # Python 3.11+

# === Parse JSON with type safety ===
raw: str = '{"user_id": 1001, "user_name": "Alice", ...}'
data: User = json.loads(raw)  # type checker knows the shape

# Access with full IDE autocompletion
print(data["user_name"])   # str
print(data["balance"])     # float
print(data["tags"])        # list[str]

# === TypedDict with total=False for all-optional ===
class PartialUpdate(TypedDict, total=False):
    user_name: str
    email: str
    is_active: bool

# === Combining Required and Optional fields ===
class BaseUser(TypedDict):
    user_id: int        # required
    user_name: str      # required

class FullUser(BaseUser, total=False):
    email: str          # optional
    is_active: bool     # optional
    tags: list[str]     # optional

# TypedDict has ZERO runtime overhead:
# no validation, no classes instantiated
# purely for static type checking with mypy/pyright

attrs:@defineとcattrsで構造化データ

attrsライブラリはdataclassesより多くの機能を提供し、バリデーターとコンバーターを含みます:

import attrs
from attrs import define, field, validators
import cattrs
import json

# === attrs with @define (modern API) ===
@define
class Address:
    street: str
    city: str
    zip_code: str

@define
class User:
    user_id: int
    user_name: str
    email: str = field(validator=validators.matches_re(r".+@.+\..+"))
    is_active: bool = True
    balance: float = 0.0
    tags: list[str] = field(factory=list)
    address: Address | None = None

# === cattrs for JSON structuring/unstructuring ===
converter = cattrs.Converter()

# Structure (dict -> attrs class)
raw = json.loads(json_string)
user = converter.structure(raw, User)

# Unstructure (attrs class -> dict)
data = converter.unstructure(user)
json_output = json.dumps(data)

# === Custom hooks for field name mapping ===
converter.register_structure_hook(
    User,
    cattrs.gen.make_dict_structure_fn(
        User,
        converter,
        user_id=cattrs.gen.override(rename="userId"),
        user_name=cattrs.gen.override(rename="userName"),
    )
)

# Now parses camelCase JSON keys to snake_case fields
camel_data = {"userId": 1, "userName": "Alice", "email": "a@b.com"}
user = converter.structure(camel_data, User)

# === Frozen attrs class (immutable) ===
@define(frozen=True)
class Config:
    host: str
    port: int
    debug: bool = False

ネストされたJSON構造の操作

実際のAPIはフラットなJSONを返すことは稀です。ほとんどのレスポンスはネストされたオブジェクトとポリモーフィック型を含みます:

ネストモデル:各ネストレベルが別のPythonクラスを生成します。Pydanticは再帰的に検証します。

List[Model]とOptionalフィールド"items": [{"id": 1}]はPythonでlist[Item]にマッピングされます。

Union型と判別ユニオン:PydanticのDiscriminatorが型安全なデシリアライゼーションを提供します。

# Discriminated union with Pydantic v2
from pydantic import BaseModel, Discriminator, Tag, TypeAdapter
from typing import Annotated, Literal, Union


class EmailNotification(BaseModel):
    type: Literal["email"]
    message: str
    recipient: str
    subject: str


class SmsNotification(BaseModel):
    type: Literal["sms"]
    message: str
    phone_number: str


class PushNotification(BaseModel):
    type: Literal["push"]
    message: str
    device_token: str
    title: str


Notification = Annotated[
    Union[
        Annotated[EmailNotification, Tag("email")],
        Annotated[SmsNotification, Tag("sms")],
        Annotated[PushNotification, Tag("push")],
    ],
    Discriminator("type"),
]

# Usage:
data = {"type": "email", "message": "Hello", "recipient": "a@b.com", "subject": "Hi"}
notif = TypeAdapter(Notification).validate_python(data)
isinstance(notif, EmailNotification)  # True

# JSON input automatically resolves to the correct type
json_str = '{"type": "sms", "message": "Alert", "phone_number": "+1234567890"}'
sms = TypeAdapter(Notification).validate_json(json_str)
isinstance(sms, SmsNotification)  # True

高度なパターン:Pydantic Settings、JSON Schema、カスタムバリデーター、計算フィールド

Pydantic SettingsはJSONファイル、環境変数、.envファイルから型付きモデルに設定を読み込みます:

from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field

class AppSettings(BaseSettings):
    model_config = SettingsConfigDict(
        env_file=".env",
        env_file_encoding="utf-8",
        json_file="config.json",
    )

    app_name: str = "MyApp"
    debug: bool = False
    database_url: str = Field(alias="DATABASE_URL")
    redis_url: str = "redis://localhost:6379"
    max_connections: int = 100

# Loads from config.json, .env, and environment variables
# Priority: env vars > .env file > json file > defaults
settings = AppSettings()
print(settings.database_url)
print(settings.max_connections)

JSON Schema生成はPydantic v2に組み込まれています。各BaseModelがJSON Schemaを生成できます:

from pydantic import BaseModel
import json

class Product(BaseModel):
    id: int
    name: str
    price: float
    tags: list[str] = []

# Generate JSON Schema
schema = Product.model_json_schema()
print(json.dumps(schema, indent=2))
# Output:
# {
#   "title": "Product",
#   "type": "object",
#   "properties": {
#     "id": {"title": "Id", "type": "integer"},
#     "name": {"title": "Name", "type": "string"},
#     "price": {"title": "Price", "type": "number"},
#     "tags": {"title": "Tags", "type": "array",
#              "items": {"type": "string"}, "default": []}
#   },
#   "required": ["id", "name", "price"]
# }

# Use schema for API docs, validation, or code generation
# FastAPI uses this automatically for OpenAPI documentation

カスタムバリデーターとシリアライゼーションエイリアスでデータ変換とフィールド名制御が可能です:

from pydantic import BaseModel, Field, field_validator, computed_field
from datetime import datetime

class User(BaseModel):
    first_name: str = Field(alias="firstName")
    last_name: str = Field(alias="lastName")
    email: str
    birth_date: datetime = Field(alias="birthDate")

    @field_validator("email")
    @classmethod
    def validate_email(cls, v: str) -> str:
        if "@" not in v:
            raise ValueError("Invalid email address")
        return v.lower().strip()

    @computed_field
    @property
    def full_name(self) -> str:
        return f"{self.first_name} {self.last_name}"

    @computed_field
    @property
    def age(self) -> int:
        today = datetime.now()
        return today.year - self.birth_date.year

# Parse from JSON with camelCase keys
user = User.model_validate_json(
    '{"firstName":"Alice","lastName":"Smith",'
    '"email":"ALICE@example.com","birthDate":"1990-05-15T00:00:00"}'
)
print(user.full_name)  # "Alice Smith"
print(user.email)      # "alice@example.com"
print(user.age)        # computed from birth_date

# Computed fields appear in serialized output
print(user.model_dump())
# {"first_name": "Alice", "last_name": "Smith",
#  "email": "alice@example.com", "birth_date": ...,
#  "full_name": "Alice Smith", "age": 35}

JSON to Python変換のベストプラクティス

堅牢なアプリケーションを構築するためのベストプラクティス:

APIコードにはPydantic:バリデーション、シリアライゼーション、JSON Schema生成を含みます。

内部構造にはdataclasses:バリデーションオーバーヘッドなしの軽量コンテナ。

Optionalフィールドを明示的に処理Optional[T]またはT | None、デフォルトNone

命名規則のミスマッチにエイリアスを使用:PydanticのField(alias="camelCase")

早期バリデーション、早期失敗:Pydanticで@field_validatorを追加。

厳格モードを使用ConfigDict(strict=True)で暗黙の型変換を防止。

JSON Schemaから型を生成datamodel-code-generatorでモデルを自動生成。

関連ツール:JSON to JavaJSON to TypeScriptJSON to Dart

JSON to JavaJSON to TypeScriptJSON to Dart

よくある質問

Pydanticとdataclasses:どちらを使うべき?

バリデーション、JSON Schema生成、FastAPI統合にはPydanticを使用。軽量コンテナにはdataclassesを使用。Pydanticはdataclassデコレーターでdataclassesにバリデーションを追加することもできます。

PythonのdictをDataclassに自動変換するには?

Pydantic:User.model_validate({"name": "Alice"})。dataclasses:dacite.from_dict(data_class=User, data=my_dict)。シンプル:User(**my_dict)。

OptionalフィールドのあるネストされたJSONをどう処理する?

Pydanticはネストモデルを自動処理。Optional[NestedModel] = Noneを使用。Pydanticは再帰的に検証。dataclassesにはdaciteまたはdataclasses-jsonを使用。

JSONからPythonデータクラスへの変換は、API、設定ファイル、データパイプラインを扱うすべてのPython開発者の基本スキルです。無料オンラインツールで即座にコードを生成し、本ガイドでベストプラクティスを参照してください。

無料オンラインツールでJSONをPythonデータクラスに即座に変換。

𝕏 Twitterin LinkedIn
この記事は役に立ちましたか?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Partner Picks

Sponsor this article

Place your product next to this developer topic with tracked clicks.

Ask about article sponsorship

This site uses cookies for analytics and to display ads. By continuing to browse, you agree. Privacy Policy