Volver al Blog
engineering||10 min de lectura

Tres formas de convertir JSON a TypeScript. Solo una es determinista.

AR
Aral Roca

Creador de Kitmul

Código en la pantalla de un MacBook Pro; tres enfoques para el mismo problema, solo uno da la misma respuesta siempre
Código en la pantalla de un MacBook Pro; tres enfoques para el mismo problema, solo uno da la misma respuesta siempre

Hay tres formas de convertir una respuesta JSON en interfaces TypeScript. Puedes escribirlas a mano, puedes preguntarle a un LLM, o puedes pasar el JSON por un convertidor determinista. He usado las tres. Dos de ellas tienen modos de falló en los que la mayoría no piensa hasta que sube un bug a producción.

El enfoque manual: lento y preciso hasta que deja de serlo

Escribir interfaces a mano funciona cuando tienes tres campos. Deja de funcionar alrededor del campo quince. Un objeto charge de Stripe tiene más de 40 propiedades. La respuesta de un pull request de GitHub supera los 100 campos una vez que cuentas los objetos anidados. Nadie los tipa a mano sin cometer errores.

El modo de falló es sutil. Abres la documentación de la API, empiezas a escribir, y para el campo veinte ya estas leyendo por encima. Era merged_at un string o un Date? Es labels un array de objetos o de strings? Adivinas, continuas, y el compilador de TypeScript confia en lo que escribiste. El sistema de tipos solo detecta errores si los tipos son correctos de entrada.

La documentación de TypeScript lo dice claro: any desactiva la verificación de tipos para ese valor. Pero una interfaz incorrecta es discutiblemente peor que any, porque te da falsa confianza. Tu IDE autocompleta campos que no existen. Tu código compila. El crash ocurre en runtime.

El enfoque LLM: rápido y probabilístico

Pegar un blob JSON en ChatGPT o Claude y pedir interfaces TypeScript es tentador. Es rápido. Maneja el anidamiento. Incluso nombra las interfaces de manera razonable la mayor parte del tiempo.

El problema es que los LLMs son probabilísticos. Dale el mismo JSON al mismo modelo dos veces y podrias obtener salidas diferentes. A veces agrega ? a campos que no son opcionales. A veces inventa un tipo union que no coincide con los datos. A veces decide que id debería ser string cuando el valor es claramente 1. He visto modelos producir Date para strings de timestamp ISO; técnicamente aspiracional, pero incorrecto si no estas parseando el string a un objeto Date primero.

Estos no son bugs del modelo. Es la naturaleza de la herramienta. Un LLM genera texto plausible basado en patrones. No parsea tu JSON como lo hace un sistema de tipos. Lo lee, aproxima cuales deberían ser los tipos, y escribe algo que parece correcto. La mayoría de las veces lo es. Pero "correcto la mayoría de las veces" y "deterministicamente correcto" son cosas diferentes cuando tus definiciones de tipos protegen el comportamiento en runtime.

También está el ángulo de privacidad. Pegar una respuesta API de producción en un LLM de terceros significa enviar tus datos al servidor de otro. Si esa respuesta contiene PII de usuarios, endpoints internos, o tokens de autenticación que se filtraron en el payload, acabas de compartirlos con un servicio externo. Para proyectos personales, a nadie le importa. Para codebases de producción con requisitos de compliance, esa es una conversacion con tu equipo de seguridad que no quieres tener.

El enfoque determinista: misma entrada, misma salida, siempre

Un convertidor determinista de JSON a TypeScript no adivina. Parsea. El algoritmo recorre el árbol JSON, inspecciona el tipo JavaScript de cada valor, y lo mapea al tipo TypeScript correspondiente. No hay aleatoriedad, no hay parámetro de temperatura, no hay modelo que pueda comportarse diferente un jueves.

El convertidor JSON a TypeScript mostrando un objeto usuario anidado con datos de perfil, enlaces sociales y array de posts; editor Monaco con resaltado de sintaxis, toggle interface/type y salida TypeScript generada
El convertidor JSON a TypeScript mostrando un objeto usuario anidado con datos de perfil, enlaces sociales y array de posts; editor Monaco con resaltado de sintaxis, toggle interface/type y salida TypeScript generada

Las reglas son mecánicas:

  • "hello" es siempre string. No a veces string, no ocasionalmente "hello" como tipo literal.
  • 42 es siempre number. No int, no float, no number | string.
  • [1, 2, 3] es siempre number[]. No Array<number>, no number[] | undefined.
  • {"a": 1} siempre genera una interfaz nombrada separada con a: number.
  • null es siempre null. No undefined, no omitido.

Mismo JSON de entrada, mismo TypeScript de salida. Ejecutalo cien veces y obtienes cien resultados identicos. Esa es la propiedad que quieres de una herramienta que genera definiciones de tipos en las que tu compilador confiara.

La salida TypeScript mostrando interface Root con campos tipados, interfaces anidadas Profile y Social, y tipo array PostsItem; el toggle interface/type permite cambiar entré ambos formatos
La salida TypeScript mostrando interface Root con campos tipados, interfaces anidadas Profile y Social, y tipo array PostsItem; el toggle interface/type permite cambiar entré ambos formatos

Lo que el algoritmo realmente hace

Bajo el capo, el convertidor hace un descenso recursivo por tu estructura JSON. Por cada valor que encuentra, llama a inferType(), que devuelve el string del tipo TypeScript. Los objetos producen nuevas entradas de interfaz en un Map. Los arrays inspeccionan sus elementos y producen un tipo uniforme (string[]) o un tipo union ((string | number)[]). Los arrays vacios se convierten en unknown[] porque no hay elemento del que inferir.

Los nombres de propiedades se convierten a PascalCase para los nombres de interfaces. Las claves que no son identificadores JavaScript válidos (guiones, espacios, digitos iniciales) se entrecomillan automáticamente. La salida puede alternarse entré declaraciones interface y type.

Un ejemplo concreto. Este JSON:

{
  "id": 1,
  "name": "Aral Roca",
  "email": "aral@example.com",
  "active": true,
  "roles": ["admin", "editor"],
  "profile": {
    "bio": "Full-stack developer",
    "avatar": "https://example.com/avatar.png",
    "social": {
      "github": "aralroca",
      "twitter": "aralroca"
    }
  },
  "posts": [
    {
      "id": 101,
      "title": "Understanding TypeScript Interfaces",
      "published": true,
      "tags": ["typescript", "tutorial"]
    }
  ],
  "createdAt": "2026-04-27T10:00:00Z"
}

Produce exactamente esto:

interface Root {
  id: number;
  name: string;
  email: string;
  active: boolean;
  roles: string[];
  profile: Profile;
  posts: PostsItem[];
  createdAt: string;
}

interface Profile {
  bio: string;
  avatar: string;
  social: Social;
}

interface Social {
  github: string;
  twitter: string;
}

interface PostsItem {
  id: number;
  title: string;
  published: boolean;
  tags: string[];
}

Cuatro interfaces. Cada campo correctamente tipado. Cada objeto anidado extraído en su propia interfaz nombrada. Sin aleatoriedad involucrada.

Interface vs. type: cuando el toggle importa

El convertidor ofrece salida tanto interface como type. La eleccion no es cosmetica.

Las interfaces soportan declaration merging; si dos interfaces comparten el mismo nombre en el mismo scope, TypeScript fusiona sus propiedades. Los types no. Para autores de bibliotecas que quieren que los consumidores extiendan tipos, las interfaces son mejor opción.

Los types manejan unions, intersecciones y mapped types de forma mas natural. Si necesitas type Result = Success | Error o componer shapes con &, la salida type te ahorra un pasó.

Para tipar respuestas API, rara vez importa. Elige lo que las reglas de linting de tu equipo impongan y sigue adelante.

Donde la inferencia determinista aún necesita revisión humana

El convertidor infiere tipos de valores, no de schemas. Eso es una feature; funciona con cualquier JSON sin requerir una spec OpenAPI ni JSON Schema. Pero significa que hay bordes donde querras ajustar:

Campos opcionales. El convertidor solo ve la muestra que proporcionas. Si un campo a veces está ausente de la respuesta, agrega ? manualmente.

Enums de string. "status": "active" se convierte en string, no en "active" | "inactive" | "suspended". Estrechalo tu.

Strings de fecha. Timestamps ISO 8601 como "2026-04-27T10:00:00Z" son string para el convertidor. Si los parseas con date-fns o dayjs, querras cambiarlos a Date en tus tipos finales.

Wrappers de paginación. Una respuesta como { data: [...], meta: { page: 1, total: 100 } } genera una interfaz Root con ambos. Renombrala a PaginatedResponse<T> y extrae Meta como generico.

Estos ajustes toman segundos. El punto es que el convertidor determinista te da una línea base correcta; las partes que necesitan juicio humano son las mismas que una máquina genuinamente no puede inferir de una sola muestra. Un LLM también las erraria; la diferencia es que el LLM podría además equivocarse en las partes fáciles.

Privacidad como feature, no como eslogan de marketing

El convertidor corre enteramente del lado del cliente. El JSON nunca sale de tu navegador. Sin llamadas al servidor, sin analitica sobre tu input, sin cuenta.

Este no es un beneficio abstracto. Muchos equipos tienen politicas de seguridad que prohiben subir código fuente o respuestas API a servicios de terceros. Eso descarta la mayoría de herramientas online. Descarta pegar respuestas de producción en chatbots LLM. Un convertidor del lado del cliente que procesa todo en una función JavaScript en tu máquina tiene cero superficie de compliance.

Abre la pestaña de red de tu navegador mientras lo usas. No veras nada enviado.

Un flujo de trabajó práctico

1. Obtener una respuesta real. Usa curl, Postman o la pestaña de red de tu navegador para capturar una respuesta API real.

2. Pegar y convertir. Abre el convertidor JSON a TypeScript, pega el JSON, copia la salida.

3. Renombrar y refinar. Cambia Root a UserResponse. Agrega ? donde sea necesario. Estrecha unions de strings.

4. Co-localizar con tu cliente API. Yo pongo los tipos en un types.ts juntó al archivo que hace la llamada fetch o axios.

5. Agregar validación en runtime. Usa Zod o Valibot para validar que la API realmente envía lo que tus tipos describen. El convertidor te da estructura; una biblioteca de esquemas te da garantías en runtime.

Todo toma menos de un minuto por endpoint.

Mas alla de respuestas API

El convertidor maneja cualquier JSON válido:

  • Archivos de configuración. Pega tsconfig.json o package.json para carga de configuración type-safe.
  • Exports de base de datos. Un documento MongoDB o fila PostgreSQL como JSON se convierte en los tipos de tu capa ORM.
  • Fixtures de test. Si escribes tests con Jest o Vitest, convertir archivos fixture asegura que tus mocks coincidan con las formas de producción.
  • Contenido CMS. Respuestas de CMS headless de Strapi, Sanity o Contentful son profundamente anidadas. Tipalas una vez; deja que el compilador detecte bugs en tus templates.

Para formatear JSON antes de convertir, el Formateador JSON maneja pretty-printing y validación. Para la dirección opuesta; limpiar HTML en algo que un LLM pueda procesar eficientemente; está el convertidor HTML a Markdown.

La matriz de trade-offs

Manual LLM Determinista
Velocidad Lenta Rápida Rápida
Corrección Depende de ti Mayormente correcta Siempre correcta para la muestra
Consistencia Variable No determinista Identica cada vez
Privacidad N/A Datos enviados al servidor Solo del lado del cliente
Campos opcionales Tu decides A veces adivina Tu decides
Estrechamiento de strings Tu decides A veces adivina Tu decides

El convertidor determinista maneja la parte mecánica; mapear valores a tipos; perfectamente. Las partes que no puede manejar (opcionalidad, enums de string, parsing de fechas) son las mismas partes que los otros enfoques tampoco manejan de forma fiable. La diferencia es que no introduce nuevos errores en las partes que si puede manejar.

La conclusión

La seguridad de tipos no es un espectro. Tus tipos son correctos o no lo son. Tipar manualmente es lento y propenso a errores a escala. Tipar con LLM es rápido pero probabilístico. La conversión determinista es rápida y correcta; dentro de los límites de lo que cualquier herramienta puede inferir de una sola muestra JSON.

Usa el convertidor JSON a TypeScript para el trabajó mecánico. Gasta tu juicio en campos opcionales, unions de strings y nombres de interfaces; las decisiones que requieren contexto que ninguna herramienta tiene.

Sin registró. Sin subida. Misma entrada, misma salida. Parte de las Utilidades de Desarrollo y Programación en Kitmul.


Foto de Florian Olivo en Unsplash.

Comparte este artículo

Boletín

Recibe Consejos de Productividad y Nuevas Herramientas Primero

Únete a creadores y desarrolladores que valoran la privacidad. En cada edición: nuevas herramientas, trucos de productividad y novedades — sin spam.

Acceso prioritario a nuevas herramientas
Cancela en cualquier momento, sin preguntas