← Todos los artículos

Cómo generar interfaces TypeScript a partir de JSON

Aprende a convertir JSON en interfaces TypeScript — a mano, con herramientas online y en código. Cubre objetos anidados, campos opcionales, arrays, tipos nullables y mantener los tipos sincronizados con tu API.

Todo desarrollador TypeScript ha estado en esta situación: estás integrando una nueva API, tienes una respuesta de muestra y necesitas interfaces tipadas antes de poder escribir una sola línea de lógica de negocio. Escribir esas interfaces a mano es tedioso, repetitivo y propenso a errores — sobre todo cuando la respuesta está profundamente anidada o tiene decenas de campos. Esta guía cubre todos los enfoques prácticos para convertir JSON a interfaces TypeScript: cómo debe ser la salida, cómo generarla automáticamente y cómo manejar casos complicados.

Por qué las interfaces TypeScript importan para datos JSON

Cuando llamas a una API en TypeScript sin anotaciones de tipo, la respuesta parseada es any. Eso significa: sin autocompletado, sin verificación de tipos y sin seguridad en tiempo de compilación. Un typo en el nombre de una propiedad se convierte en un bug en tiempo de ejecución en vez de un error de compilación.

// Sin tipos — todo es any
const response = await fetch('/api/users/42');
const user = await response.json(); // user: any
console.log(user.naem); // typo — no hay error en compilación

// Con una interfaz tipada
interface User {
  id: number;
  name: string;
  email: string;
}

const response = await fetch('/api/users/42');
const user: User = await response.json();
console.log(user.naem); // Error: Property 'naem' does not exist on type 'User'

La interfaz añade cero costo en runtime — solo existe en tiempo de compilación. Pero cambia por completo la experiencia de desarrollo.

Qué produce una conversión de JSON a TypeScript

Cada objeto JSON se convierte en una interface TypeScript, cada primitiva JSON se mapea a su equivalente TypeScript, y los arrays infieren el tipo de sus elementos.

// Entrada JSON
{
  "id": 42,
  "name": "Alice Johnson",
  "email": "alice@example.com",
  "active": true,
  "score": 98.5,
  "address": {
    "street": "123 Main St",
    "city": "New York",
    "zip": "10001"
  },
  "tags": ["admin", "editor"],
  "orders": [
    { "id": 101, "total": 49.99, "status": "shipped" },
    { "id": 102, "total": 99.00, "status": "pending" }
  ]
}
// Salida TypeScript
interface Root {
  id: number;
  name: string;
  email: string;
  active: boolean;
  score: number;
  address: Address;
  tags: string[];
  orders: Order[];
}

interface Address {
  street: string;
  city: string;
  zip: string;
}

interface Order {
  id: number;
  total: number;
  status: string;
}

Cada objeto anidado se vuelve una interfaz con nombre. El nombre proviene de la clave padre — address se vuelve Address, los elementos de orders se vuelven Order. Eso mantiene los tipos legibles y componibles.

Las reglas de mapeo de tipos

Entender cómo se mapean los tipos JSON a TypeScript hace que la salida sea predecible y te permite ajustarla con confianza.

Valor JSONTipo TypeScript
"hello"string
42, 3.14number
true, falseboolean
nullnull
{}una interface con nombre
['a', 'b']string[]
[1, 'two'](number | string)[]
[] (vacío)unknown[]

Un matiz: TypeScript no tiene tipo entero. Tanto 42 como 3.14 se convierten en number. Si la distinción importa en tu caso, puedes usar tipos branded o añadir un comentario.

Método 1: usar un conversor JSON-a-TypeScript online

Para tareas puntuales — un endpoint nuevo, un prototipo rápido — una herramienta online es el camino más rápido. El conversor JSON-a-TypeScript de fixjson realiza la conversión completa en tu navegador:

  1. Pega tu JSON en el panel de entrada. La herramienta acepta JSON válido o JSON roto con problemas comunes como comas finales y comillas simples — repara el JSON antes de convertirlo.
  2. Las interfaces TypeScript aparecen al instante a la derecha — sin botones que pulsar.
  3. Copia la salida y pégala directamente en tu archivo TypeScript.

Todo se ejecuta localmente. Ningún dato se envía a ningún servidor — importante cuando el JSON con el que trabajas contiene claves de API, datos de usuario o configuración interna.

Método 2: escribir la conversión en TypeScript

Cuando necesitas generar tipos programáticamente — como parte de un script de build o un generador de código — puedes escribir la lógica tú mismo. Aquí tienes una implementación mínima pero completa:

function jsonToTypeScript(value: unknown, rootName = 'Root'): string {
  const interfaces: string[] = [];
  const seen = new Set<string>();

  function cap(s: string) {
    return s ? s.charAt(0).toUpperCase() + s.slice(1) : 'Unknown';
  }

  function singular(name: string) {
    if (/ies$/i.test(name)) return name.slice(0, -3) + 'y';
    if (/[^s]s$/i.test(name)) return name.slice(0, -1);
    return name + 'Item';
  }

  function getType(val: unknown, name: string): string {
    if (val === null) return 'null';
    if (typeof val === 'boolean') return 'boolean';
    if (typeof val === 'number') return 'number';
    if (typeof val === 'string') return 'string';
    if (Array.isArray(val)) {
      if (val.length === 0) return 'unknown[]';
      const objs = val.filter(
        (v): v is Record<string, unknown> =>
          v !== null && typeof v === 'object' && !Array.isArray(v),
      );
      if (objs.length > 0) {
        const itemName = cap(singular(name));
        buildInterface(itemName, objs);
        return `${itemName}[]`;
      }
      const types = [...new Set(val.map((v) => getType(v, name)))];
      return types.length === 1 ? `${types[0]}[]` : `(${types.join(' | ')})[]`;
    }
    if (typeof val === 'object') {
      const iName = cap(name);
      buildInterface(iName, [val as Record<string, unknown>]);
      return iName;
    }
    return 'unknown';
  }

  function buildInterface(name: string, objs: Record<string, unknown>[]): void {
    if (seen.has(name)) return;
    seen.add(name);
    const allKeys = [...new Set(objs.flatMap((o) => Object.keys(o)))];
    const props: string[] = [];
    for (const key of allKeys) {
      const present = objs.filter((o) => key in o);
      const optional = present.length < objs.length ? '?' : '';
      const propType = getType(present[0][key], cap(key));
      const safeKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `"${key}"`;
      props.push(`  ${safeKey}${optional}: ${propType};`);
    }
    interfaces.push(`interface ${name} {\n${props.join('\n')}\n}`);
  }

  const rootType = getType(value, rootName);
  const defs = interfaces.reverse().join('\n\n');
  if (rootType !== cap(rootName)) {
    return `type ${cap(rootName)} = ${rootType};\n\n${defs}`.trim();
  }
  return defs;
}

Uso en un script de build Node.js:

import * as fs from 'fs';

const json = JSON.parse(fs.readFileSync('api-response.json', 'utf8'));
const typescript = jsonToTypeScript(json, 'UserResponse');
fs.writeFileSync('types/user.ts', typescript);

Método 3: quicktype (CLI y librería)

quicktype es la herramienta open-source más completa para generar tipos TypeScript a partir de JSON. Maneja casos límite que los generadores simples pasan por alto y soporta múltiples formatos de salida.

npm install -g quicktype

# Desde un archivo local
quicktype api-response.json -o types/user.ts --lang typescript

# Desde una URL
quicktype https://api.example.com/users/42 -o types/user.ts --lang typescript

# Desde stdin
cat api-response.json | quicktype --lang typescript

quicktype también genera funciones de validación en runtime usando librerías como io-ts o zod — útil cuando necesitas validar datos entrantes, no solo tiparlos estáticamente.

Manejar casos complicados

Campos nullables

Las APIs JSON suelen devolver null para valores faltantes. Un generador que ve null produce null como tipo — pero lo que normalmente quieres es string | null:

// Generado (primer paso — demasiado estrecho)
interface Root {
  name: string;
  middleName: null;
}

// Lo que deberías escribir
interface Root {
  name: string;
  middleName: string | null;
}

Revisa siempre los campos nullables a mano tras la generación. Si tienes varias respuestas de muestra donde un campo a veces es string y a veces null, un buen generador inferirá string | null automáticamente.

Opcional vs nullable

TypeScript distingue entre field?: string (la clave puede no estar) y field: string | null (la clave siempre está, el valor puede ser null):

interface Order {
  id: number;
  total: number;
  discount?: number;   // clave ausente en algunas respuestas
  note: string | null; // clave siempre presente, valor puede ser null
}

Los generadores que analizan varias muestras JSON pueden inferir esta distinción con precisión. Los generadores de una sola muestra marcan las claves ausentes como opcionales pero no pueden distinguir una clave faltante de un valor null.

Claves que no son identificadores válidos

Algunas claves JSON contienen caracteres no válidos como nombres de propiedad TypeScript:

// JSON
{ "content-type": "application/json", "x-request-id": "abc123" }

// TypeScript generado — válido pero requiere notación con corchetes
interface Root {
  "content-type": string;
  "x-request-id": string;
}

// Acceso con notación de corchetes
obj["content-type"]

Si controlas la API, prefiere claves camelCase para evitar esto del todo. Si no, la sintaxis de propiedad entrecomillada es TypeScript válido y funciona correctamente.

Arrays con tipos mixtos

// JSON
{ "values": [1, "two", true, null] }

// Generado
interface Root {
  values: (number | string | boolean | null)[];
}

Tipos unión como este son técnicamente correctos pero normalmente delatan un problema de diseño en la API. Si los ves, verifica si el campo es realmente mixto o si los datos de muestra son engañosos.

Usar los tipos generados de forma segura

Generar una interfaz es solo el primer paso. La interfaz es un contrato en tiempo de compilación — no valida nada en runtime. response.json() devuelve any, y TypeScript te dejará castearlo a tu interfaz sin verificación real.

Para APIs internas donde controlas ambos lados, esto suele estar bien. Para APIs externas o datos del usuario, valida en runtime. La opción recomendada es Zod:

import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  active: z.boolean(),
  address: z.object({
    street: z.string(),
    city: z.string(),
    zip: z.string(),
  }),
  tags: z.array(z.string()),
  orders: z.array(z.object({
    id: z.number(),
    total: z.number(),
    status: z.string(),
  })),
});

type User = z.infer<typeof UserSchema>; // tipo TypeScript derivado del schema

const raw = await response.json();
const user = UserSchema.parse(raw); // lanza si la forma no coincide

El enfoque de Zod es lo mejor de ambos mundos: el schema es la única fuente de verdad para el tipo TypeScript y la validación en runtime. Si la API cambia de forma incompatible, te enteras en el punto donde los datos entran en tu sistema.

Más allá de las interfaces: validadores y clientes tipados

Una interfaz generada es un contrato en tiempo de compilación. El siguiente paso son las librerías que te dan tanto el tipo como la verificación en runtime desde una sola fuente:

  • Zod y io-ts — define un schema, infiere el tipo TypeScript a partir de él y valida los datos entrantes en la frontera. Si la API cambia, tu build (y tus logs de error) te lo dicen de inmediato.
  • tRPC y wrappers de fetch tipados (p. ej. openapi-fetch) — el procedimiento del servidor o la definición OpenAPI es la fuente de verdad, y el cliente recoge tipos coincidentes automáticamente, sin paso JSON-a-TS por medio.
  • ts-pattern y uniones discriminadas — una vez que tus datos tengan un campo de etiqueta (p. ej. { kind: "circle" | "square", ... }), el pattern matching exhaustivo detecta casos olvidados en tiempo de compilación.

Mantener los tipos sincronizados con la API

Los tipos generados tienen fecha de caducidad. Las APIs cambian — un campo se renombra, aparece un nuevo campo requerido o un campo string pasa a ser nullable. Tus interfaces quedan desfasadas en silencio.

Genera desde la spec OpenAPI, no desde respuestas de muestra

Si la API tiene una definición OpenAPI (Swagger), openapi-typescript genera tipos directamente desde la spec:

npm install -g openapi-typescript
openapi-typescript https://api.example.com/openapi.json -o types/api.ts

Esto es mucho más fiable que generar a partir de una respuesta de muestra, porque la spec documenta cada campo, incluyendo los opcionales, las variantes nullables y los valores de enumeración.

Regenera tipos en CI

Añade un paso a tu pipeline de CI que regenere los tipos desde la spec o una muestra conocida, y luego ejecute tsc --noEmit para detectar roturas. Si la API cambia, el build falla antes de que el cambio rompedor llegue a producción.

Preguntas frecuentes

¿Cómo convierto JSON en una interfaz TypeScript?

Pega el JSON en un conversor online como la herramienta JSON-a-TypeScript de fixjson para salida instantánea, ejecuta quicktype en un paso del build, o genera desde una spec OpenAPI con openapi-typescript para código de larga vida.

¿Cómo se mapean los tipos JSON a TypeScript?

Strings → string, todos los números → number (TypeScript no tiene tipo entero), booleanos → boolean, objetos → interfaces con nombre y los arrays infieren el tipo de elemento. Mira la tabla de mapeo de arriba y los seis tipos de datos JSON.

¿Cómo manejo campos nullables u opcionales?

Usa field?: T cuando la clave pueda no estar y field: T | null cuando la clave esté siempre pero su valor pueda ser null. Los generadores que analizan varias muestras lo infieren; las herramientas de muestra única necesitan revisión manual.

¿Las interfaces TypeScript validan JSON en runtime?

No — las interfaces se borran en tiempo de compilación. Para seguridad en runtime con datos externos, valida con Zod o describe la forma con JSON Schema.

Conclusión

Convertir JSON a interfaces TypeScript es algo que todo desarrollador TypeScript hace con regularidad. El enfoque adecuado depende del contexto: para una integración puntual rápida, un conversor online te pone en marcha en menos de un minuto. Para un pipeline de build, quicktype o un script propio automatizan el proceso. Para código de producción de larga vida, generar tipos desde una spec OpenAPI y validar en runtime con Zod te da las garantías más fuertes.

Sea cual sea el método, la interfaz generada es un punto de partida — revisa los campos nullables, añade comentarios para propiedades no obvias y endurece las restricciones donde tus reglas de negocio sean más estrictas que lo que implica la muestra JSON. Tipos que reflejan los datos con precisión hacen que toda la codebase sea más fácil de mantener.

Si el JSON con el que trabajas tiene errores de sintaxis antes incluso de intentar convertirlo, arréglalo primero. Un generador TypeScript — como cualquier parser JSON — requiere entrada sintácticamente válida.