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(整数) | 42 | int | Pythonでは任意精度 |
| number(小数) | 3.14 | float、Decimal | 金融データにはDecimal |
| boolean | true | bool | 直接対応 |
| null | null | None | Optional[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データクラスコンバーターは体系的なプロセスに従います:
- JSON構造を解析:Python組み込みの
jsonモジュールでパース。 - フィールド型を推論:各キーバリューペアのPython型を決定。
- クラス名を生成:PascalCaseとsnake_caseに変換。
- ネストオブジェクトを処理:各オブジェクトが別クラスを生成。
- 配列を処理:要素型を分析。
- 型アノテーションを追加:PEP 484/604構文。
- ソースコードを出力:フォーマットされた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 = FalseTypedDict:ランタイムオーバーヘッドなしの型ヒント
型安全だがゼロランタイムオーバーヘッドの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/pyrightattrs:@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 Java、JSON to TypeScript、JSON 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開発者の基本スキルです。無料オンラインツールで即座にコードを生成し、本ガイドでベストプラクティスを参照してください。