Convertir JSON a Python dataclasses es una de las tareas mas comunes en el desarrollo Python moderno. Cuando tu aplicacion recibe una respuesta JSON de una API REST, necesitas dataclasses Python, modelos Pydantic o TypedDicts bien estructurados. Esta guia completa cubre mapeo de tipos, estrategias de parsing JSON, Pydantic, attrs, TypedDict y mejores practicas.
Prueba nuestro convertidor gratuito de JSON a Python Dataclass en linea.
Que es la conversion de JSON a Python Dataclass?
JSON es el formato de intercambio de datos dominante para APIs web. Python, aunque dinamicamente tipado, se beneficia enormemente de definiciones de tipos explicitas via dataclasses, modelos Pydantic y TypedDicts. La conversion JSON a Python dataclass analiza un documento JSON y produce las clases Python correspondientes.
En una aplicacion FastAPI tipica, un manejador de ruta recibe el cuerpo de la solicitud HTTP como cadena JSON. El framework debe convertir ese JSON en objetos Python usando el BaseModel de Pydantic. Un convertidor JSON a clase Python automatiza la creacion de estos modelos.
La misma conversion es esencial en Django REST Framework, en pipelines de datos y en herramientas CLI que leen archivos de configuracion JSON. El proceso es identico: inspeccionar la estructura, determinar tipos, manejar anidamiento.
JSON a Python: Mapeo de tipos
Entender como los tipos JSON se mapean a tipos Python es la base de cualquier conversion:
| Tipo JSON | Ejemplo | Tipo(s) Python | Notas |
|---|---|---|---|
| string | "hello" | str | Siempre str; datetime para fechas ISO |
| number (entero) | 42 | int | Precision arbitraria en Python |
| number (decimal) | 3.14 | float, Decimal | Decimal para datos financieros |
| boolean | true | bool | Correspondencia directa |
| null | null | None | Optional[T] o T | None |
| array | [1,2] | list[T] | list[T] en Python 3.9+ |
| object | {"k":"v"} | Clase anidada | Clases tipadas preferidas |
Al generar dataclasses Python desde JSON, la eleccion entre Optional[str] y str | None depende de la version de Python. Para valores monetarios, usa siempre Decimal.
Como funciona la conversion JSON a Python
Un convertidor JSON a Python dataclass sigue un proceso sistematico:
- Analizar la estructura JSON: Parsing con el modulo
jsonintegrado de Python. - Inferir tipos de campo: Determinar el tipo Python para cada par clave-valor.
- Generar nombres de clase: Conversion a PascalCase y snake_case.
- Manejar objetos anidados: Cada objeto anidado genera una clase separada.
- Manejar arrays: Analisis del tipo de elemento.
- Agregar anotaciones de tipo: Sintaxis PEP 484/604.
- Producir codigo fuente: Codigo Python formateado.
Ejemplos de codigo: JSON a Python con Pydantic, dataclasses, TypedDict y attrs
Pydantic v2: BaseModel, Field y validadores
Pydantic es la biblioteca JSON a Python mas utilizada. FastAPI la usa por defecto:
# === 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
El modulo dataclasses integrado de Python ofrece una alternativa ligera:
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: anotaciones de tipo sin sobrecarga
Cuando necesitas parsing JSON Python con seguridad de tipo pero cero sobrecarga en tiempo de ejecucion:
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 y cattrs para datos estructurados
La biblioteca attrs ofrece mas funciones que dataclasses, incluyendo validadores y conversores:
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 = FalseTrabajar con estructuras JSON anidadas
Las APIs reales rara vez devuelven JSON plano. La mayoria contiene objetos anidados y tipos polimorficos:
Modelos anidados: Cada nivel de anidamiento produce una clase Python separada. Pydantic valida recursivamente.
List[Model] y campos Optional: Un campo como "items": [{"id": 1}] se mapea a list[Item] en Python.
Tipos Union y uniones discriminadas: El Discriminator de Pydantic ofrece deserializacion type-safe.
# 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) # TruePatrones avanzados: Pydantic Settings, JSON Schema, validadores y campos calculados
Pydantic Settings permite cargar configuracion desde archivos JSON, variables de entorno y archivos .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)La generacion de JSON Schema esta integrada en Pydantic v2. Cada BaseModel puede producir un 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 documentationValidadores personalizados y alias de serializacion permiten transformar datos y controlar nombres de campos:
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}Mejores practicas para conversion JSON a Python
Sigue estas mejores practicas para aplicaciones robustas:
Usa Pydantic para codigo API: Validacion, serializacion y generacion JSON Schema incluidas.
Usa dataclasses para estructuras internas: Contenedores ligeros sin sobrecarga de validacion.
Maneja campos opcionales explicitamente: Optional[T] o T | None con defecto None.
Usa alias para convenciones de nombres: Field(alias="camelCase") en Pydantic.
Valida temprano, falla rapido: Agrega @field_validator en Pydantic.
Usa modo estricto: ConfigDict(strict=True) previene coercion implicita.
Genera tipos desde JSON Schema: Usa datamodel-code-generator para auto-generar modelos.
Herramientas relacionadas: JSON a Java, JSON a TypeScript, JSON a Dart.
JSON to JavaJSON to TypeScriptJSON to Dart
Preguntas frecuentes
Pydantic o dataclasses: cual debo usar?
Usa Pydantic para validacion, generacion JSON Schema e integracion FastAPI. Usa dataclasses para contenedores ligeros. Pydantic ofrece un decorador dataclass que agrega validacion a dataclasses estandar.
Como convierto un dict Python a dataclass?
Pydantic: User.model_validate({"name": "Alice"}). Dataclasses: dacite.from_dict(data_class=User, data=my_dict). Simple: User(**my_dict).
Como manejar JSON anidado con campos opcionales?
Pydantic maneja modelos anidados automaticamente. Usa Optional[NestedModel] = None. Pydantic valida recursivamente. Para dataclasses, usa dacite o dataclasses-json.
Convertir JSON a Python dataclasses es una habilidad fundamental. Usa nuestra herramienta gratuita para generacion instantanea y consulta esta guia para mejores practicas.
Convierte JSON a Python dataclasses al instante con nuestra herramienta gratuita.