DevToolBox무료
블로그

JSON to 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은 웹 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.14float, Decimal금융 데이터에는 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은 가장 널리 사용되는 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 파싱이 필요할 때:

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 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 개발자의 기본 기술입니다. 무료 온라인 도구로 즉시 코드를 생성하고 본 가이드에서 모범 사례를 참조하세요.

무료 온라인 도구로 JSON을 Python 데이터클래스로 즉시 변환하세요.

𝕏 Twitterin LinkedIn
도움이 되었나요?

최신 소식 받기

주간 개발 팁과 새 도구 알림을 받으세요.

스팸 없음. 언제든 구독 해지 가능.

Try These Related Tools

PYJSON to PythonTSJSON to TypeScript{ }JSON Formatter

Related Articles

JSON to Java 클래스 변환기: POJO, Jackson, Gson, Lombok 가이드

JSON을 Java 클래스로 온라인 변환. Jackson, Gson, Lombok으로 POJO 생성 방법을 코드 예제와 함께 알아보세요.

JSON에서 TypeScript로: 예제와 함께하는 완벽 가이드

JSON 데이터를 TypeScript 인터페이스로 자동 변환하는 방법을 배웁니다. 중첩 객체, 배열, 선택적 필드, 모범 사례를 다룹니다.

JSON에서 Go Struct로: 매핑 전략과 모범 사례

JSON에서 Go struct로의 변환을 마스터하세요. struct 태그, 중첩 타입, omitempty, 커스텀 마샬링, 실전 패턴을 다룹니다.