Skip to content
jsonforge.app
Volver al blog
Guía7 min de lectura

Generar tipos TypeScript a partir de JSON: una guía práctica

Escribir a mano una interfaz de TypeScript para una respuesta de API de 40 campos es tedioso y propenso a errores — un typo en un nombre de campo rompe silenciosamente la seguridad de tipos en lugar de lanzar un error de compilación. Generar la interfaz directamente a partir de una muestra real de JSON es más rápido y preciso, siempre que entiendas dónde la inferencia tiene que adivinar.

Cómo funciona la inferencia de JSON a TypeScript

Un generador recorre el árbol de valores JSON y mapea cada tipo de JavaScript a su equivalente de TypeScript: string sigue siendo string, number sigue siendo number, boolean sigue siendo boolean, null se convierte en null (o se fusiona en una unión), los arrays se convierten en T[] donde T se infiere de los elementos, y los objetos se convierten en interfaces anidadas.

Una muestra JSON y su interfaz inferida.
json
{
  "id": 42,
  "email": "ada@example.com",
  "isActive": true,
  "roles": ["admin", "editor"],
  "profile": {
    "displayName": "Ada",
    "avatarUrl": null
  }
}
Salida TypeScript inferida.
typescript
interface Root {
  id: number;
  email: string;
  isActive: boolean;
  roles: string[];
  profile: {
    displayName: string;
    avatarUrl: string | null;
  };
}

Dónde la inferencia tiene que adivinar

Campos opcionales frente a siempre presentes: una sola muestra JSON no puede decirle a un generador si avatarUrl a veces falta por completo (lo que debería ser avatarUrl?: string | null) o siempre está presente pero a veces es null. Dale al generador unas cuantas muestras representativas, o marca manualmente un campo como opcional tras la generación si sabes que la API lo omite bajo ciertas condiciones.

Números que en realidad son IDs frente a cantidades: JSON tiene un único tipo number, pero TypeScript no puede distinguir un ID entero de un precio de coma flotante sin contexto extra. Algunos generadores ofrecen tipos con marca o unions literales para esto; la mayoría simplemente emiten number y te dejan la distinción a ti.

Arrays vacíos: un array sin ejemplos dentro ("tags": []) no le da al generador nada de lo que inferir el tipo de elemento — normalmente recurre a unknown[] o any[]. Apunta el generador a una muestra donde el array esté poblado, o anótalo manualmente después.

Uniones de literales de cadena: un campo status que siempre es uno de "pending" | "active" | "closed" se inferirá como simple string salvo que el generador vea todos los valores posibles en la muestra — e incluso entonces, la mayoría de generadores usan por defecto el tipo más amplio string salvo que actives explícitamente la inferencia de unions literales.

Fechas: JSON no tiene tipo de fecha — los timestamps siempre son cadenas (ISO 8601) o números (epoch de Unix). Un generador tipará un campo de fecha como string o number, no como Date; todavía tienes que analizarlo tú mismo tras el fetch.

La misma idea en otros lenguajes

La lógica de inferencia — recorrer el árbol, mapear tipos, nombrar objetos anidados — es la misma sin importar el lenguaje destino; solo cambia la sintaxis de salida. Un struct de Go recibe el mismo mapeo de campos con etiquetas de struct para los nombres de clave JSON; un dataclass de Python recibe type hints y un envoltorio opcional Optional[...]; una clase de Java o C# recibe campos tipados y getters/setters o records. Si tu equipo trabaja en varios lenguajes contra la misma API, generar los tipos para cada uno desde la misma muestra JSON los mantiene honestos entre sí.

La misma forma como struct de Go.
go
type Root struct {
	ID       int     `json:"id"`
	Email    string  `json:"email"`
	IsActive bool    `json:"isActive"`
	Roles    []string `json:"roles"`
	Profile  struct {
		DisplayName string  `json:"displayName"`
		AvatarURL   *string `json:"avatarUrl"`
	} `json:"profile"`
}

Antes de confiar en los tipos generados

Los tipos generados son un punto de partida sólido, no un contrato final. Antes de hacer commit: confirma los campos opcionales contra la documentación real de la API (no solo una muestra), sustituye los campos string sueltos que en realidad son enums por unions literales, comprueba que los campos de array vacío hayan recibido un tipo de elemento real a partir de una muestra mejor, y regenera cada vez que la API aguas arriba añada o renombre un campo en lugar de parchear la interfaz a mano.

FAQ

¿Puede un generador JSON a TypeScript manejar un campo que a veces es string y a veces number?
Sí, si ve ambas variantes entre las muestras que se le dan — debería inferir string | number. Con una sola muestra, solo puede tipar lo que vio. Esta es la razón principal para generar a partir de múltiples cargas útiles representativas en lugar de un único ejemplo del camino feliz.
¿Deberían las interfaces generadas usar `interface` o `type`?
Funcionalmente casi idénticas para este caso de uso. `interface` es más común para formas de objeto y admite declaration merging; `type` es más flexible para unions e intersecciones. Ambas sirven para formas de respuesta de API — elige la que coincida con la convención existente de tu base de código.
¿Cómo mantengo los tipos generados sincronizados a medida que la API evoluciona?
Trata la generación como un paso repetible, no un copiar-pegar único: mantén una muestra real de respuesta en el repositorio (o recogiéndola de un endpoint de staging) y regenera la interfencia cada vez que cambie la muestra, en lugar de editar a mano el archivo generado. Algunos equipos lo integran en CI para detectar el desvío automáticamente.
¿Funciona para JSON profundamente anidado o recursivo, como una estructura de árbol?
El anidamiento profundo genera interfaces anidadas sin problema. Las estructuras verdaderamente recursivas (un comentario que puede contener respuestas de la misma forma) necesitan un tipo autorreferencial, que la mayoría de generadores no inferirá automáticamente de una sola muestra — típicamente tendrás que escribir a mano la interfaz recursiva una vez y reutilizarla.

Prueba estas herramientas

Artículos relacionados