Skip to content
Volver al blog
Tutoriales

JSON a Python: dataclass, Pydantic y TypedDict (guía 2026)

Convierte JSON a clases de Python correctamente: dataclass, Pydantic v2 o TypedDict, tipado Optional, alias camelCase y errores a evitar. Gratis, en línea.

12 min de lectura

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:

  1. 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.
  2. Elige dataclass, Pydantic o TypedDict. Usa el selector de salida para cambiar de estilo y renombra el Root por defecto a algo con significado, como User o ApiResponse.
  3. Copia o descarga. Toma el código Python generado con un clic y coloca models.py directamente 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 JSONTipo de Python
"text"str
42int (precisión arbitraria, sin desbordamiento)
3.14, 2e3float
true / falsebool
nullOptional[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óndataclassPydantic v2TypedDict
DependenciaBiblioteca estándarpip install pydanticBiblioteca estándar (typing)
Validación en tiempo de ejecuciónNoSí: valida y convierteNo: solo sugerencias
Costo en tiempo de ejecuciónLigero (objeto real)Algo (validación)Cero (es un dict)
Parseo anidado automáticoNo, manualSí, model_validate recorre en profundidadNo, es un dict
Alias camelCaseConserva la clave originalField(alias=...)Sintaxis funcional
Mejor paraEstructuras internasRespuestas de API, webhooks, configuraciónTipar 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:

TareaPydantic v2 (salida de la herramienta)Pydantic v1
Parsear un dictmodel_validate(data)parse_obj(data)
Parsear JSON crudomodel_validate_json(text)parse_raw(text)
Configurar el modelomodel_config = ConfigDict(...)class Config interna
Serializar a dictmodel_dump()dict()
Serializar a JSONmodel_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.

EnfoqueMejor paraNota
Conversor en línea (esta herramienta, jsonlint, codeshack)Conversiones puntuales, payloads sensibles, cero instalaciónInfiere a partir de una muestra, del lado del cliente por completo
quicktypeSalida multilenguaje, generación de código en pipelinesTambién se basa en muestras; el soporte de Python es más limitado en alias y elección de modo
datamodel-code-generatorGenerar Pydantic a partir de un Schema JSON u OpenAPILa entrada es un esquema, no una muestra: más autoritativo cuando tienes uno
Escribir las clases a manoPayloads diminutos, aprender la sintaxisControl 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. Optional solo 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 a model_validate de Pydantic o usa dacite.
  • Un campo siempre en null se vuelve Optional[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_validate quedará indefinido. Actualiza a v2 o reescríbelo como parse_obj.
  • Una fecha dejada como str no hará aritmética de fechas. Cambia el campo a datetime, o en Pydantic usa un campo datetime para 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.

Etiquetas: python json pydantic type-safety developer-tools

Artículos relacionados

Ver todos los artículos