JSON a Python: dataclass, Pydantic y TypedDict (guía 2026)
Pega tu JSON en el conversor de JSON a Python y copia las clases que genera. Ese es el camino rápido para convertir JSON a Python: nada que instalar, nada que subir, todo se ejecuta en tu navegador. Para un modelo puntual o para echar un vistazo rápido a la respuesta de una API que no conoces, terminas en segundos.
El problema difícil es el que un conversor no puede resolver por ti. Infiere los tipos correctos, pero no puede tomar la decisión que más importa: cuál de las tres salidas quieres en realidad. Un dataclass, un BaseModel de Pydantic v2 y un TypedDict pueden describir exactamente el mismo JSON y, aun así, se diferencian en la fuerza de su seguridad de tipos, en su comportamiento en tiempo de ejecución y en cómo cargas los datos en ellos. Si eliges mal, o cargas con la dependencia de Pydantic para nada, o das por hecho que un dataclass valida tu entrada cuando en silencio no lo hace.
Esta guía cubre la matriz de decisión para las tres opciones, cómo el conversor infiere los tipos, el manejo de camelCase en cada modo, cómo parsear JSON con la clase que generas (incluida la trampa de los objetos anidados que sorprende a mucha gente), Pydantic v2 frente a v1 y los casos límite que conviene conocer antes de confiar en el resultado.
Cómo convertir JSON a Python
Convertir JSON a Python toma tres pasos:
- Pega tu JSON. Suelta un objeto, un array o la respuesta cruda de una API en el cuadro de entrada. La conversión se ejecuta al instante y por completo del lado del cliente.
- Elige dataclass, Pydantic o TypedDict. Usa el selector de salida para cambiar de estilo y renombra el
Rootpor defecto a algo con significado, comoUseroApiResponse. - Copia o descarga. Toma el código Python generado con un clic y coloca
models.pydirectamente en tu proyecto.
Ese es todo el recorrido de la conversión. Si tu entrada está minificada o no estás seguro de que sea válida, pásala primero por un formateador de JSON para que el conversor tenga un JSON limpio y bien formado que leer. El resto de esta guía explica la salida para que puedas arreglar los casos que una herramienta no puede inferir por su cuenta.
Cómo se asignan los tipos de JSON a Python
Cada valor de JSON tiene su equivalente en Python, y la correspondencia es directa:
| Valor JSON | Tipo de Python |
|---|---|
"text" | str |
42 | int (precisión arbitraria, sin desbordamiento) |
3.14, 2e3 | float |
true / false | bool |
null | Optional[T] / Optional[Any] |
[1, 2, 3] | List[T] |
{ ... } | una clase con nombre |
Toma un payload REST típico:
{ "id": 101, "name": "Ada Lovelace", "email": "ada@example.com", "active": true, "roles": ["admin", "user"] }
En modo dataclass, el conversor produce una clase lista para usar:
from dataclasses import dataclass
from typing import List
@dataclass
class Root:
id: int
name: str
email: str
active: bool
roles: List[str]
Un matiz distingue a Python. Su int es de precisión arbitraria, así que un snowflake de Discord o cualquier ID mayor a 2^53 se queda como un simple int sin pérdida. JavaScript perdería precisión con ese mismo valor, y Rust te obligaría a elegir entre i64, u64 y f64. Solo un token escrito con punto decimal o con exponente se infiere como float. Un campo que siempre es null no puede tiparse solo a partir de la muestra, así que recae en Optional[Any]; pega un payload representativo con un valor real para obtener algo más preciso que Any.
dataclass frente a Pydantic v2 frente a TypedDict: ¿cuál deberías usar?
Esta es la decisión que el conversor te devuelve. La versión corta: usa un dataclass cuando quieras un contenedor de datos interno sin dependencias, Pydantic v2 cuando parsees o valides entrada no confiable y TypedDict cuando solo necesites sugerencias de tipos estáticas sobre diccionarios que ya tienes.
| Dimensión | dataclass | Pydantic v2 | TypedDict |
|---|---|---|---|
| Dependencia | Biblioteca estándar | pip install pydantic | Biblioteca estándar (typing) |
| Validación en tiempo de ejecución | No | Sí: valida y convierte | No: solo sugerencias |
| Costo en tiempo de ejecución | Ligero (objeto real) | Algo (validación) | Cero (es un dict) |
| Parseo anidado automático | No, manual | Sí, model_validate recorre en profundidad | No, es un dict |
| Alias camelCase | Conserva la clave original | Field(alias=...) | Sintaxis funcional |
| Mejor para | Estructuras internas | Respuestas de API, webhooks, configuración | Tipar código heredado con diccionarios |
Los tres destinos se ven casi idénticos para una forma pequeña como {"id": 101, "name": "Ada Lovelace", "active": true}. Un dataclass de Python generado desde JSON te da un objeto de la biblioteca estándar:
from dataclasses import dataclass
@dataclass
class Root:
id: int
name: str
active: bool
La versión de Pydantic a partir de JSON cambia a BaseModel, que se ve similar pero justifica su costo en tiempo de ejecución:
from pydantic import BaseModel
class Root(BaseModel):
id: int
name: str
active: bool
Y un TypedDict de Python a partir de JSON describe la forma de un dict simple sin asignar nada nuevo:
from typing import TypedDict
class Root(TypedDict):
id: int
name: str
active: bool
Los mismos campos, tres contratos muy diferentes. El valor de generar los tres a partir de una sola muestra es que puedes alternar entre ellos en la herramienta y ajustar el modo a la tarea: Pydantic cuando parseas datos que no controlas, un dataclass cuando los datos son internos y TypedDict cuando solo quieres que mypy entienda un dict que ya pasas de un lado a otro.
Cómo el conversor infiere las clases
Tres reglas cubren casi todo lo que le des: una clase por cada forma de objeto, fusión clave por clave para los arrays y un manejo cuidadoso de los valores ausentes.
Inferencia estructural: una clase con nombre por objeto
Cada forma de objeto distinta se convierte en su propia clase con nombre. Los objetos anidados no se incrustan en línea; se elevan a definiciones separadas y referenciadas:
{ "repo": "pydantic", "owner": { "login": "samuelcolvin", "id": 100 } }
from dataclasses import dataclass
@dataclass
class Owner:
login: str
id: int
@dataclass
class Root:
repo: str
owner: Owner
El owner anidado se convierte en su propia clase Owner, referenciada por campo. Fíjate en el orden: la clase hija se emite antes que el padre que la usa, así que el módulo se ejecuta sin from __future__ import annotations. Las formas idénticas también se deduplican, de modo que dos campos con la misma estructura comparten una sola clase en lugar de producir copias.
Fusión de arrays y campos Optional
Cuando pasas un array de objetos, el conversor los fusiona clave por clave en un único tipo de elemento. Una clave que está presente en algunos elementos pero ausente en otros se vuelve Optional:
{ "users": [{ "id": 1, "nick": "x" }, { "id": 2 }] }
from typing import List, Optional, TypedDict
class User(TypedDict):
id: int
nick: Optional[str]
class Root(TypedDict):
users: List[User]
id aparece en cada elemento, así que se mantiene obligatorio. nick está ausente en el segundo usuario, así que se vuelve Optional[str]. Esto es exactamente por qué un solo objeto es una muestra débil: la herramienta solo puede marcar un campo como Optional cuando de verdad ve que la clave falta en algún lugar. Dale un array representativo y la inferencia reflejará la forma real.
Optional, None y arrays vacíos
Algunos valores no cargan un tipo por sí solos, y el conversor es honesto al respecto. Una clave ausente en algunos elementos del array se vuelve Optional[T]. Un campo que siempre es null se vuelve Optional[Any], porque null de JSON por sí solo no te dice nada sobre el tipo previsto. Un array vacío o uno con tipos de elemento mezclados recae en List[Any]. Ninguno de estos es un error; es la herramienta negándose a adivinar. Reemplaza cada Any por un tipo concreto en cuanto tengas una muestra que lo revele, y recuerda que el int de precisión arbitraria de Python significa que un ID grande nunca te obliga a usar un tipo numérico más ancho, como sí ocurriría en Rust o JavaScript.
Manejo de claves camelCase en cada modo
Las claves de JSON suelen estar en camelCase, mientras que los campos idiomáticos de Python están en snake_case. Aquí no hay una única respuesta correcta, así que cada modo lo resuelve de forma distinta, y vale la pena entender la diferencia antes de copiar la salida.
dataclass. Un dataclass no tiene un mecanismo de alias integrado. Para que Root(**data) siga funcionando, el conversor conserva la clave original como nombre del campo siempre que sea un identificador válido de Python, así que publicRepos se queda como publicRepos. Forzarlo a snake_case rompería **data con un TypeError.
Pydantic v2. Aquí obtienes el resultado idiomático. Los campos se renombran a snake_case con un Field(alias=...) que apunta de vuelta a la clave JSON exacta, más model_config = ConfigDict(populate_by_name=True) para que puedas construir el modelo por nombre de campo o por alias:
from pydantic import BaseModel, ConfigDict, Field
class Root(BaseModel):
model_config = ConfigDict(populate_by_name=True)
login: str
public_repos: int = Field(alias="publicRepos")
created_at: str = Field(alias="createdAt")
Eso es lo que permite que Root.model_validate(api_json) procese ida y vuelta payloads reales en camelCase mientras tu código se lee de forma natural.
TypedDict. Las claves que son identificadores simples se usan directamente. En cuanto aparece una clave que no es un identificador, el conversor cambia a la sintaxis funcional TypedDict('User', {...}) para preservar la clave exacta, algo que cubre la siguiente sección.
Palabras clave de Python y claves que no son identificadores
Las claves de JSON son solo cadenas, así que nada impide que una API envíe class, from o first-name. Cada una sería un error de sintaxis como atributo de Python a secas, así que el conversor las sanea.
Una clave que es una palabra clave de Python recibe un guion bajo al final: class se vuelve class_, from se vuelve from_. En modo Pydantic también conserva un Field(alias=...) para que el modelo siga leyendo la clave original tal como llega:
from pydantic import BaseModel, ConfigDict, Field
class Root(BaseModel):
model_config = ConfigDict(populate_by_name=True)
class_: str = Field(alias="class")
from_: str = Field(alias="from")
Las claves con guiones, espacios o un dígito inicial se sanean a identificadores válidos en los modos dataclass y Pydantic. TypedDict es el único modo que puede conservar la clave literal, usando la forma funcional para que "first-name" sobreviva sin tocar:
from typing import TypedDict
Root = TypedDict("Root", {"first-name": str, "2fa": bool})
El resultado siempre se ejecuta, y en modo TypedDict la clave se mantiene byte por byte tal como la envió la API.
Parsear JSON con la clase generada
Generar la clase es la mitad del trabajo. Cargar JSON en ella es donde los tres modos divergen de forma marcada, y donde una suposición muy común falla.
dataclass. Para un objeto plano, json.loads más Root(**data) funciona directamente:
import json
from dataclasses import dataclass
from typing import List
@dataclass
class Root:
id: int
name: str
roles: List[str]
data = json.loads('{"id": 101, "name": "Ada Lovelace", "roles": ["admin"]}')
root = Root(**data)
print(root.name, root.roles)
Pero un dataclass no recorre en profundidad. Si el JSON tiene objetos anidados, Root(**data) llena el nivel superior y deja cada hijo como un dict simple, no como la clase Owner que generaste. Para parsear un árbol completo, construye los hijos tú mismo, usa una biblioteca como dacite o pydantic.dataclasses, o cambia la herramienta al modo Pydantic. Esta es la trampa que la mayoría de los conversores nunca menciona.
Pydantic v2. Una sola llamada parsea y valida el árbol entero:
import json
from pydantic import BaseModel
class Root(BaseModel):
id: int
name: str
root = Root.model_validate(json.loads('{"id": 101, "name": "Ada Lovelace"}'))
print(root.name)
Una discrepancia de tipos lanza un ValidationError claro en lugar de pasar en silencio. Esa es la recompensa por la dependencia: el modelo es un validador en tiempo de ejecución, así que una llamada a model_validate que retorna es una garantía de que los datos coincidieron, no solo la creencia de un verificador de tipos de que así fue.
TypedDict. En tiempo de ejecución, un TypedDict es un dict común. Anotar data: Root = json.loads(text) es puramente para el verificador estático; no se valida nada cuando el código se ejecuta. Si necesitas una imposición real, ejecuta mypy en CI o pásate a Pydantic. Elegir un modo es elegir cuánta garantía en tiempo de ejecución quieres, y cuando un contrato necesita ir más allá de los tipos, la guía de validación con JSON Schema cubre cómo imponer la estructura de principio a fin.
Pydantic v2 frente a v1: qué cambió
El conversor emite sintaxis v2. Muchas bases de código todavía usan v1, y pegar salida v2 en un proyecto v1 falla por métodos renombrados, no por lógica. Aquí está la correspondencia:
| Tarea | Pydantic v2 (salida de la herramienta) | Pydantic v1 |
|---|---|---|
| Parsear un dict | model_validate(data) | parse_obj(data) |
| Parsear JSON crudo | model_validate_json(text) | parse_raw(text) |
| Configurar el modelo | model_config = ConfigDict(...) | class Config interna |
| Serializar a dict | model_dump() | dict() |
| Serializar a JSON | model_dump_json() | json() |
El comportamiento de los alias también se volvió más estricto: en v2 necesitas populate_by_name=True junto a Field(alias=...) para construir un modelo tanto por el nombre del campo como por el alias, y por eso la herramienta lo incluye. La regla práctica es corta. Actualiza a v2 si puedes, ya que es más rápida y es el objetivo del desarrollo actual; de lo contrario, reescribe model_validate como parse_obj y ConfigDict como una Config interna.
json-to-python frente a quicktype, datamodel-code-generator y hacerlo a mano
No hay una única mejor forma de generar una clase de Python a partir de JSON. Depende de dónde viva el JSON y de con qué cuentes para empezar.
| Enfoque | Mejor para | Nota |
|---|---|---|
| Conversor en línea (esta herramienta, jsonlint, codeshack) | Conversiones puntuales, payloads sensibles, cero instalación | Infiere a partir de una muestra, del lado del cliente por completo |
| quicktype | Salida multilenguaje, generación de código en pipelines | También se basa en muestras; el soporte de Python es más limitado en alias y elección de modo |
| datamodel-code-generator | Generar Pydantic a partir de un Schema JSON u OpenAPI | La entrada es un esquema, no una muestra: más autoritativo cuando tienes uno |
| Escribir las clases a mano | Payloads diminutos, aprender la sintaxis | Control total, pero tedioso y fácil de que se desincronice |
La distinción clave: tanto un conversor en línea como quicktype infieren los tipos a partir de una muestra de JSON, mientras que datamodel-code-generator lee una especificación JSON Schema u OpenAPI como su fuente de verdad. Usa las herramientas basadas en muestras para explorar y para casos puntuales; recurre a la basada en esquemas cuando ya existe un contrato. Si tu base de código es TypeScript en lugar de Python, el mismo enfoque basado en muestras aplica con el conversor de JSON a TypeScript, y la guía de interfaces de JSON a TypeScript cubre ese lado en profundidad. Para el ángulo de la validación en tiempo de ejecución en un lenguaje compilado y fuertemente tipado, la guía de structs de JSON a Rust ofrece un contraste útil: serde es el Pydantic de Rust.
Errores comunes al generar Python a partir de JSON
Las clases generadas son un punto de partida. Ten en cuenta lo siguiente antes de confiar en la salida con datos reales.
- Una sola muestra rara vez revela todas las formas.
Optionalsolo puede inferirse de elementos de array que de verdad difieren. Pega un array representativo para que la opcionalidad sea precisa y no una conjetura a partir de un objeto afortunado. - Un dataclass no valida.
Root(**data)solo asigna campos; no verifica tipos ni recorre nada en profundidad. Si necesitas imposición, usa Pydantic. - El JSON anidado rompe
Root(**data). Solo se llena el nivel superior; los hijos quedan como diccionarios. Cambia amodel_validatede Pydantic o usa dacite. - Un campo siempre en
nullse vuelveOptional[Any]. Trátalo como un marcador de posición, no como un opcional normal, y asígnale un tipo real en cuanto conozcas uno. - Salida v2 en un proyecto v1.
model_validatequedará indefinido. Actualiza a v2 o reescríbelo comoparse_obj. - Una fecha dejada como
strno hará aritmética de fechas. Cambia el campo adatetime, o en Pydantic usa un campodatetimepara que parsee ISO 8601 por ti.
Preguntas frecuentes
¿Debería usar un dataclass, Pydantic o TypedDict para JSON?
Usa un dataclass para un contenedor de datos interno sin dependencias. Usa Pydantic v2 cuando parsees o valides entrada no confiable, porque impone los tipos en tiempo de ejecución. Usa TypedDict para añadir sugerencias estáticas a un dict que ya tienes, sin costo en tiempo de ejecución. Cuando los datos vienen de fuera de tu control, opta por Pydantic de forma predeterminada.
¿Pydantic valida el JSON en tiempo de ejecución?
Sí. Root.model_validate(data) valida y convierte los tipos mientras el código se ejecuta, lanzando un ValidationError ante una entrada incorrecta. Un dataclass no hace ninguna de las dos cosas: Root(**data) solo asigna campos. Un TypedDict tampoco hace ninguna de las dos; es una sugerencia para verificadores estáticos como mypy y se comporta como un dict simple en tiempo de ejecución.
¿Cómo tipo un campo que el JSON a veces omite?
Tipalo como Optional[T]. Cuando una clave aparece solo en algunos elementos del array, el conversor la marca como Optional automáticamente. En Pydantic, dale al campo un valor por defecto como = None para que una clave ausente no dispare un error de validación. Un campo de dataclass puede tener un valor por defecto de la misma forma con field(default=None).
¿Por qué mi dataclass no pudo parsear JSON anidado?
Porque Root(**data) no recorre en profundidad. Llena los campos del nivel superior y deja los objetos anidados como diccionarios simples en lugar de las clases hijas que generaste. Para parsear el árbol completo en una sola llamada, cambia a model_validate de Pydantic o añade recursión con dacite o pydantic.dataclasses.
¿En qué tipo de Python se convierte un entero JSON grande?
int. Los enteros de Python son de precisión arbitraria, así que un snowflake o un ID de Discord mayor a 2^53 se queda como un simple int sin pérdida. Nunca te topas con la pérdida de precisión que tiene JavaScript, y nunca tienes que elegir entre i64 y u64 como te obliga Rust.
¿Cómo se manejan las claves JSON en camelCase en cada modo?
Un dataclass conserva la clave original para que Root(**data) siga funcionando. Pydantic renombra los campos a snake_case con Field(alias="camelKey") más populate_by_name=True, de modo que procesa ida y vuelta datos reales de una API. TypedDict usa la sintaxis funcional TypedDict('Name', {...}) para preservar cualquier clave que no sea un identificador válido.
¿Cómo manejo una clave JSON que es una palabra clave de Python como class?
El conversor añade un guion bajo al final, convirtiendo class en class_ para que el código sea válido. En modo Pydantic también agrega Field(alias="class"), que mapea el campo de vuelta a la clave original, así que el nombre es legal y el modelo sigue leyendo el payload real.
¿Mi JSON es privado al usar un conversor de JSON a Python en línea?
Sí. La conversión se ejecuta 100% en tu navegador con JavaScript. Tu JSON, incluidos tokens, IDs y datos de clientes, nunca sale de la página y nunca se envía a un servidor. Incluso funciona sin conexión una vez que la página se ha cargado. Cuando estés listo, prueba el conversor de JSON a Python con tu propio payload.