JSON을 Python 데이터클래스로 변환하는 것은 현대 Python 개발에서 가장 일반적인 작업 중 하나입니다. REST API에서 JSON 응답을 받을 때 데이터를 안전하게 다루기 위해 잘 구조화된 Python 데이터클래스, Pydantic 모델 또는 TypedDict가 필요합니다. 이 종합 가이드는 타입 매핑, Python JSON 파싱 전략, Pydantic, attrs, TypedDict, 모범 사례를 다룹니다.
무료 온라인 JSON to Python 데이터클래스 변환기를 사용해 보세요.
JSON to Python 데이터클래스 변환이란?
JSON은 웹 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은 가장 널리 사용되는 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 파싱이 필요할 때:
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 개발자의 기본 기술입니다. 무료 온라인 도구로 즉시 코드를 생성하고 본 가이드에서 모범 사례를 참조하세요.