DevToolBox免费
博客

JSON转Python数据类:Pydantic、dataclasses、TypedDict和attrs完整指南

17分钟阅读作者 DevToolBox

JSON 转换为 Python dataclass 是现代 Python 开发中最常见的任务之一。当应用程序从 REST API 接收 JSON 响应时,你需要结构良好的 Python dataclass、Pydantic 模型或 TypedDict 来安全地处理数据。无论你是构建 FastAPI 后端、Django REST 服务还是数据管道,可靠的 JSON 转 Python dataclass 转换器都能节省大量手动编码时间。本指南全面介绍类型映射、Python JSON 解析策略、Pydantic、attrs、TypedDict 以及从 JSON 生成 Python 类的最佳实践。

立即试用我们的免费在线 JSON 转 Python Dataclass 转换工具。

什么是 JSON 转 Python Dataclass 转换?

JSON 是 Web API、配置文件和 NoSQL 数据库的主流数据交换格式。Python 虽然是动态类型语言,但通过 dataclass、Pydantic 模型和 TypedDict 的显式类型定义可以极大受益。JSON 转 Python dataclass 转换通过分析 JSON 文档并生成具有正确类型字段、默认值和验证逻辑的对应 Python 类来弥合这一差距。

在典型的 FastAPI 应用中,路由处理器接收 JSON 字符串格式的 HTTP 请求体。在业务逻辑处理数据前,框架必须使用 Pydantic 的 BaseModel 将 JSON 转换为 Python 对象。没有正确定义的模型,你只能使用原始字典,没有类型安全、自动补全和验证。JSON 转 Python 类转换器自动创建这些数据模型。

在 Django REST Framework 中序列化器解析 API 载荷、在数据工程管道中处理来自 Kafka 或 S3 的 JSON 记录、以及在读取 JSON 配置文件的 CLI 工具中,同样需要这种转换。底层过程都是相同的:检查 JSON 结构,确定每个字段的类型,处理嵌套和数组,生成干净的带类型注解的 Python 源代码。

JSON 到 Python:类型映射

理解 JSON 类型如何映射到 Python 类型是任何 JSON 转 Python 转换的基础:

JSON 类型示例Python 类型说明
string"hello"str始终映射为 str;ISO 日期字符串使用 datetime
number(整数)42intPython int 支持任意精度
number(浮点数)3.14floatDecimal金融数据使用 Decimal
booleantruebool直接映射为 True/False
nullnullNone使用 Optional[T]T | None
array[1, 2, 3]list[T]Python 3.9+ 使用 list[T]
object{"k": "v"}嵌套类或 dict优先使用强类型嵌套类

从 JSON 生成 Python dataclass 时,选择 Optional[str] 还是 str | None 取决于 Python 版本。Python 3.10+ 原生支持管道语法。货币值始终使用 Decimal 而非 float。字典转 dataclass 的转换同样受益于正确的类型注解。

JSON 转 Python 转换的工作原理

JSON 转 Python dataclass 转换器遵循系统化流程将原始 JSON 转换为带类型的 Python 源代码:

  1. 解析 JSON 结构:使用 Python 内置 json 模块解析输入 JSON,构建嵌套字典/列表结构。
  2. 推断字段类型:对每个键值对确定 Python 类型。字符串变为 str,整数变为 int,浮点数变为 float,null 变为 Optional
  3. 生成类名:将 JSON 键名转换为 PascalCase 类名和 snake_case 字段名。
  4. 处理嵌套对象:每个嵌套 JSON 对象生成一个单独的 Python dataclass 或 Pydantic 模型。
  5. 处理数组:分析数组元素确定元素类型,对象数组生成 list[ElementClass]
  6. 添加类型注解:使用 PEP 484/604 语法添加 Python 类型提示。
  7. 输出源代码:生成格式化的 Python 源代码,包含导入、类定义和字段声明。

代码示例:使用 Pydantic、dataclasses、TypedDict 和 attrs 实现 JSON 转 Python

Pydantic v2:BaseModel、Field 和验证器

Pydantic 是 Python 生态中最广泛使用的 JSON 转 Python 库,FastAPI 默认使用它。以下是使用 Pydantic v2 的完整示例:

# === 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 模块提供了轻量级的 JSON 转 Python 类替代方案。结合 dataclasses-jsondacite 库可实现 JSON 解析:

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 为字典提供结构化类型。适合直接使用 json.loads() 且需要 IDE 自动补全和 mypy 验证的场景:

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 出现,提供更多功能,包括验证器、转换器和基于 slots 的类。结合 cattrs 提供强大的 JSON 到 Python 的结构化和解构化:

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 dataclass 需要仔细规划:

嵌套模型:每层嵌套生成一个单独的 Python 类。Pydantic 中嵌套模型会递归验证。在 dataclasses 中,你必须显式处理嵌套字典到对象的转换。

List[Model] 和 Optional 字段"items": [{"id": 1}, {"id": 2}] 在 Python 中映射为 list[Item]。可能为 null 或缺失的字段使用 Optional[T],默认值为 None

Union 类型和判别联合:当 JSON 字段根据判别符持有不同对象形状时,Pydantic 的 DiscriminatorTag 提供类型安全的反序列化。

# 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 文件加载配置到单个类型化模型中。适用于 FastAPI 或 Django 项目的应用配置:

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 都可以生成用于 API 文档、验证和其他语言代码生成的 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

自定义验证器和序列化别名允许在解析时转换数据并控制输出字段名。计算字段(Pydantic v2)让你定义出现在序列化输出中的派生属性:

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 转 Python 转换最佳实践

将 JSON 转换为 Python dataclass 时,遵循以下最佳实践以构建健壮、可维护的应用:

API 代码使用 Pydantic:构建 REST API 或处理外部 JSON 数据时,Pydantic 提供开箱即用的验证、序列化和 JSON Schema 生成。使用 model_validate_json() 直接解析字符串。

内部数据结构使用 dataclasses:需要无验证开销的轻量级数据容器时,Python 内置的 @dataclass 装饰器就够了。添加 frozen=True 实现不可变性。

显式处理可选和可空字段:使用 Optional[T]T | None。设置默认值为 None。永远不要直接使用可变默认值如 []{}

使用别名处理命名约定不匹配:JSON API 通常使用 camelCase,而 Python 偏好 snake_case。使用 Pydantic 的 Field(alias="camelCase") 或全局配置别名生成器。

尽早验证,快速失败:在 Pydantic 中添加 @field_validator,在 dataclasses 中添加 __post_init__ 检查,在构造时验证数据。

使用严格模式控制类型强制:Pydantic v2 的 ConfigDict(strict=True) 防止隐式类型转换。在数据完整性关键的场景(如金融应用)中启用。

从 JSON Schema 生成类型:如果 API 提供 OpenAPI/JSON Schema 规范,使用 datamodel-code-generator 自动生成 Pydantic 模型。

相关工具推荐:JSON 转 Java 用于 Spring Boot 后端开发,JSON 转 TypeScript 用于前端接口,JSON 转 Dart 用于 Flutter 应用。

JSON to JavaJSON to TypeScriptJSON to Dart

常见问题

Pydantic 和 dataclasses 哪个更适合 JSON 转 Python?

需要数据验证、JSON Schema 生成或 FastAPI 集成时使用 Pydantic。Pydantic 在构造时验证数据,原生支持 datetime 和 UUID 等复杂类型,并自动生成 API 文档。需要无验证开销的轻量级数据容器时使用 dataclasses。dataclasses 是标准库的一部分(Python 3.7+),无外部依赖。

如何自动将 Python dict 转换为 dataclass?

Pydantic 模型使用 model_validate() 直接转换字典:User.model_validate({"name": "Alice", "age": 30})。标准 dataclasses 使用 dacite 库:dacite.from_dict(data_class=User, data=my_dict)。dacite 处理嵌套结构、Optional 字段和 Union 类型。对于简单情况,可以直接解包字典:User(**my_dict)。

如何在 Python 中处理带可选字段的嵌套 JSON?

Pydantic 中嵌套模型自动处理。定义嵌套 BaseModel 并引用为字段类型。可选嵌套对象使用 Optional[NestedModel] = None。Pydantic 递归验证整个树。dataclasses 中需在 __post_init__ 中手动转换嵌套字典,或使用 dacite/dataclasses-json。Optional 字段始终提供 None 默认值。

将 JSON 转换为 Python dataclass 是每个处理 API、配置文件或数据管道的 Python 开发者的基本技能。从带验证和 JSON Schema 的 Pydantic v2 模型到轻量级 dataclasses 和 TypedDicts,正确方法取决于项目需求。使用我们的免费在线 JSON 转 Python 转换器即时生成代码。

使用我们的免费在线工具即时将 JSON 转换为 Python dataclass。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

PYJSON to PythonTSJSON to TypeScript{ }JSON Formatter

相关文章

JSON转Java类转换器:POJO、Jackson、Gson和Lombok完整指南

在线将JSON转换为Java类。学习使用Jackson、Gson、Lombok和Java Records从JSON生成POJO的方法。

JSON 转 TypeScript:完整指南与示例

学习如何自动将 JSON 数据转换为 TypeScript 接口。涵盖嵌套对象、数组、可选字段和最佳实践。

JSON 转 Go Struct:映射策略与最佳实践

掌握 JSON 到 Go struct 的转换。涵盖 struct tags、嵌套类型、omitempty、自定义序列化和实际应用模式。