JSON a las interfaces de TypeScript: una guía práctica para escribir respuestas de API
Aprenda a convertir JSON en interfaces TypeScript precisas, manejar campos opcionales, nulos, uniones y matrices, y mantener los tipos sincronizados con las APIs reales.
· 5 min de lectura
Cada proyecto de TypeScript que habla con una API finalmente se enfrenta a la misma pregunta: ¿cómo describir la forma de los datos que llegan como JSON sin escribir? Dejar respuestas como any arroja el principal beneficio de TypeScript. Escribir interfaces a mano para una carga útil de 200 líneas es tedioso y propenso a errores. Esta guía recorre un flujo de trabajo confiable para generar interfaces a partir de JSON, refinarlas y mantenerlas honestas con el tiempo.
Por qué los tipos para JSON importan
Cuando llamas a await res.json(), TypeScript te da any. A partir de ese momento, errores tipográficos como user.adress.city se compilan sin quejas y solo fallan en tiempo de ejecución, a menudo en producción y a menudo para un subconjunto de usuarios cuyos datos difieren. Una respuesta escrita le da autocompletar, refactorización segura y compilar errores de tiempo cuando el backend renombra un campo.
Los tipos también actúan como documentación. Un nuevo miembro del equipo que lee interface Order { id: string; items: OrderItem[]; total: number; couponCode?: string } aprende más en cinco segundos que desplazándose a través de una muestra de respuesta.
Paso 1: Comience con una muestra real y representativa
Los tipos generados son tan buenos como la muestra que se alimenta. Antes de convertir, recoger una respuesta que ejerce tantos campos como sea posible:
- Elija un registro con campos opcionales rellenados, como un pedido que tenga un cupón, una dirección de envío y un mensaje de regalo.
- Incluye matrices con al menos un elemento; una matriz vacía no le dice nada a un generador sobre su contenido.
- Si un campo puede ser
null, trate de incluir un ejemplo donde sea nulo y uno donde no lo sea.
Copie el cuerpo de la respuesta de la pestaña Red de su navegador o de curl, pégalo en un JSON formatador y confirme que es válido. Luego cambie el modo a → TypeScript.
Paso 2: Generar el primer borrador
Dado este dato:
{
"id": "ord_9f2",
"createdAt": "2026-09-30T12:01:44Z",
"customer": { "id": 42, "email": "ana@example.com" },
"items": [
{ "sku": "TSHIRT-M", "qty": 2, "price": 19.5 },
{ "sku": "MUG", "qty": 1, "price": 12 }
],
"coupon": null
}
Un generador produce algo así como:
export interface Customer {
id: number;
email: string;
}
export interface Item {
sku: string;
qty: number;
price: number;
}
export interface Root {
id: string;
createdAt: string;
customer: Customer;
items: Item[];
coupon: null;
}
Este es un punto de partida sólido, pero refleja sólo lo que contenía una muestra. Los siguientes pasos lo convierten en un tipo que coincide con el contrato de la API.
Paso 3: Renombrar y refinar
Los generadores nombran las interfaces anidadas después de las claves. Renombrarlos a términos de dominio: Root se convierte en Order, Item se convierte en OrderItem. Luego revise cada campo con estas preguntas.
** ¿Puede faltar?** Si la API omite una clave en algunas respuestas, marque como opcional con ?. Opcional (coupon?: Coupon) y nulable (coupon: Coupon | null) son diferentes: el primero significa que la clave puede estar ausente, el segundo significa que siempre está presente pero puede contener null. Muchas APIs hacen ambas cosas, que se expresan como coupon?: Coupon | null.
** ¿Fue null el único valor visto?** Un campo escrito literalmente como null casi siempre significa que la muestra no fue representativa. Sustituye con el tipo real más | null.
** ¿Es una cadena realmente una cadena? ** Las fechas llegan como cadenas ISO 8601. Mantenerlos como string en el tipo de transporte; convertirlos a Date pertenece a una capa de mapeo. Puedes documentar la intención con un alias tipo: type ISODateString = string.
¿Es un número un enum? Si status es siempre uno de los pocos valores, use una unión de literales: status: "pending" | "paid" | "shipped". Esto da una comprobación exhaustiva de switch.
** ¿Son las ID seguras como números? ** ID de base de datos por encima de 2 ^ 53 pierden precisión en JavaScript. Si su backend utiliza enteros de 64 bits, pídale que envíe IDs como cadenas y escriba como string.
El resultado refinado:
export type ISODateString = string;
export interface Order {
id: string;
createdAt: ISODateString;
status: "pending" | "paid" | "shipped" | "cancelled";
customer: Customer;
items: OrderItem[];
coupon?: Coupon | null;
}
Paso 4: Manejar matrices con contenido mixto
Algunas APIs devuelven matrices heterogéneas, como una fuente de actividad con diferentes tipos de eventos. Un generador ingenuo produce una única interfaz con cada campo opcional, lo que pierde información. Una unión discriminada es mucho mejor:
type FeedEvent =
| { type: "comment"; id: string; text: string }
| { type: "like"; id: string; userId: string }
| { type: "follow"; id: string; followerId: string };
Ahora TypeScript reduce el tipo automáticamente dentro de if (event.type === "comment"). Al convertir una matriz mixta, la herramienta une los tipos de elementos inferidos; usa esa salida como una pista para diseñar la unión discriminada a mano.
Paso 5: Validación en tiempo de ejecución
Los tipos de TypeScript desaparecen en el momento de compilar. Si el backend cambia, su código todavía recibe la nueva forma y sus tipos en silencio. Para los datos que cruzan un límite de confianza, añadir la validación en tiempo de ejecución con una biblioteca de esquemas como Zod, Valibot o ArkType y derivar el tipo estático del esquema:
import { z } from "zod";
const OrderSchema = z.object({
id: z.string(),
createdAt: z.string().datetime(),
items: z.array(z.object({ sku: z.string(), qty: z.number().int(), price: z.number() })),
coupon: z.object({ code: z.string() }).nullable().optional(),
});
export type Order = z.infer<typeof OrderSchema>;
const order = OrderSchema.parse(await res.json());
Todavía se puede usar el generador para arrancar: generar la interfaz, y luego traducirlo campo por campo en el esquema.
Paso 6: Prefiera la fuente de la verdad cuando existe
Si su API publica un esquema OpenAPI (Swagger) o GraphQL, genere tipos a partir de eso en lugar de a partir de muestras. Herramientas como openapi-typescript y GraphQL Code Generator producen tipos exactos incluyendo opcionalidad y enum, y pueden ejecutarse en CI para que los tipos se actualicen cada vez que el esquema cambia. La generación basada en muestras brilla para las API de terceros sin esquemas, prototipos rápidos, webhooks y servicios heredados.
¿Interfaz o tipo?
Para las formas de objetos, interface y type son casi intercambiables. Las interfaces se pueden extender y fusionar, y los mensajes de error se refieren a ellos por su nombre, lo que ayuda con objetos grandes. Se requieren alias de tipo para uniones, tuplas y tipos mapeados. Una convención común son las interfaces para las formas de objetos y los alias de tipo para todo lo demás.
Trampas comunes
- ** Escribir todo como opcional.** Se siente seguro pero obliga a los controles nulos en todas partes y oculta violaciones reales del contrato. Haga que los campos sean opcionales sólo cuando la API los omita.
- Confianza en una sola muestra. Verifique la documentación o varias respuestas antes de finalizar la opción.
- ** Mezcla de transporte y tipos de dominio.** Mantenga un
OrderDTOen bruto que refleje JSON y lo mapee en un dominioOrdercon objetosDatereales y campos calculados. - ** Olvidando los envases de paginación.** Muchas APIs envuelven listas en
{ data: [...], nextCursor: "..." }. Modela esto una vez como un genérico:interface Page<T> { data: T[]; nextCursor: string | null }.
Una lista de verificación rápida
- Captura una muestra rica y real y válida.
- Generar interfaces con el convertidor [JSON a TypeScript] ((/tools/json).
- Cambiar el nombre de los tipos al idioma del dominio.
- Corrección opcional versus campos nulas, uniones literales y tipos de ID.
- Reemplazar las filas mixtas con sindicatos discriminados.
- Añadir validación de tiempo de ejecución para datos externos.
- Cambiar a la generación basada en esquemas cuando existe un esquema OpenAPI o GraphQL.
Siguiendo estos pasos toma unos minutos por punto final y paga muchas veces más en autocompletar, más seguros de los refactores y los errores capturados antes de que lleguen a los usuarios.
Esta página se tradujo automáticamente del inglés. Si encuentras un error, avísanos.