JSON a TypeScript

Genera interfaces de TypeScript desde JSON: arrays fusionados, claves opcionales, uniones de tipos mixtos, null aparte — todo en tu navegador.

Entrada
TypeScript
export interface Root {
  id: number
  name: string
  active: boolean
  address: Address
  roles: string[]
  posts: Post[]
  tags: unknown[]
}

export interface Address {
  city: string
  zip: null
}

export interface Post {
  id: number
  title: string
  views: number | string
  pinned?: boolean
}

Interfaces: 3

Tipos inferidos de los datos, no adivinados

Un documento JSON no lleva sus propios tipos: lleva valores, y los tipos hay que leerlos de vuelta a partir de ellos. Eso es fácil para un solo objeto y sorprendentemente sutil para una colección: la forma que quieres no es la forma de ningún registro concreto, sino la forma que todo registro debe satisfacer. Esta herramienta infiere interfaces de TypeScript a partir de una muestra de JSON mirando todo lo que la muestra contiene, de modo que los tipos que produce describen la totalidad de tus datos y no la primera fila que quedó encima.

La conversión va en un solo sentido. Convertir los tipos de vuelta a JSON supondría inventar valores, y aquí el objetivo es el contrario: el documento pegado es la fuente de verdad, y cada declaración se deriva de él. Pega una respuesta de una API, un archivo de configuración o una línea de log, y lee la interfaz que de otro modo habrías escrito a mano.

Cómo se fusionan los arrays

Las decisiones interesantes ocurren todas en los arrays. Un conversor ingenuo mira el primer elemento y se detiene, lo que hace que todo campo que ve por casualidad parezca obligatorio y omite todo campo que no ve. Esta herramienta, en cambio, fusiona todos los elementos en un solo tipo, y de ahí se desprenden tres cosas:

  • Una clave presente en algunos elementos pero ausente en otros se vuelve opcional, escrita con un signo de interrogación. Si la mitad de tus registros tienen "middleName" y la mitad no, el campo es "middleName?", que es exactamente lo que un consumidor debe manejar.
  • Una clave cuyo valor difiere en tipo entre elementos se vuelve una unión. Un campo que es un número en un registro y una cadena en otro se tipa como "number | string", no porque sea prolijo, sino porque es lo que los datos realmente contienen y lo que tu código debe aceptar.
  • Un objeto anidado dentro de los elementos se fusiona de la misma manera, recursivamente, y se extrae a su propia interfaz. Diez elementos de un array que llevan cada uno un "address" producen una sola interfaz Address que describe los diez.

Cuando el propio documento es un array en el nivel superior, la raíz se convierte en un alias a una interfaz de elemento —por ejemplo "type Root = RootItem[]"— con el tipo del elemento fusionado escrito debajo.

Null, opcional y por qué son diferentes

Es tentador tratar un null igual que una clave ausente, y es un error. En TypeScript "name?: string" significa que la propiedad puede faltar; "name: string | null" significa que siempre está pero puede contener null. Son contratos distintos, y un consumidor los comprueba de forma distinta: "in" frente a una comparación de valor. Esta herramienta los mantiene separados: un null explícito en los datos se vuelve un miembro de unión "| null", colocado al final para que "string | null" se lea como esperas, mientras que una clave que simplemente falta en algunos registros se vuelve opcional. Un campo que es ambas cosas —null en un registro, ausente en otro— sale como ambas, "field?: T | null", porque las dos cosas son ciertas de tus datos.

Los objetos anidados se vuelven interfaces con nombre

En lugar de incrustar una forma anidada dentro de su padre, cada objeto se extrae a su propia interfaz cuyo nombre se deriva de la clave bajo la que está. Un objeto "user" se vuelve una interfaz User; un "address" dentro de él se vuelve una interfaz Address a la que User hace referencia. Los tipos profundamente incrustados son difíciles de leer e imposibles de reutilizar, y las interfaces con nombre son lo que habrías escrito tú mismo. Los elementos de un array se ponen en singular cuando se puede —"users" da un User, "categories" un Category— y una clave que no se pluraliza recibe el sufijo Item para que el elemento tenga un nombre propio.

Si dos objetos distintos tomaran el mismo nombre —dos "data" sin relación, por ejemplo— el segundo recibe un sufijo en vez de fusionarse, de modo que las formas distintas siguen siendo distintas. El objeto raíz se emite primero y puedes renombrarlo; la elección entre salida "interface" y "type" es un interruptor, ya que algunas bases de código prefieren los alias de tipo en todo momento.

El repliegue a unknown

Algunos valores no llevan ninguna información de tipo. Un array vacío podría contener cualquier cosa; un objeto vacío no tiene claves que describir. En vez de recurrir a "any" —que apaga la comprobación de tipos para todo lo que sigue— la herramienta recae en "unknown": un array vacío se vuelve "unknown[]", un objeto vacío se vuelve "Record<string, unknown>". La diferencia importa. "any" deja pasar errores en silencio; "unknown" obliga al consumidor a estrechar el valor antes de usarlo, de modo que el tipo inferido sigue siendo honesto sobre lo que la muestra dijo y lo que no.

Son los tipos que ajustarías a mano en cuanto sepas qué debe contener la colección vacía, pero hasta que los datos lo digan, "unknown" es la respuesta honesta, y es la que mantiene seguros el resto de tus tipos.

Sobre qué se ejecuta, y dónde

Todo ocurre en tu navegador. El JSON se analiza y los tipos se infieren en tu propio dispositivo; nada de lo que pegues se sube, almacena ni registra. Eso hace que la herramienta sea segura de usar con una respuesta de API real o un archivo de configuración con secretos dentro: la muestra nunca sale de la página. La salida es TypeScript corriente que puedes pegar directamente en un archivo "d.ts" o en un módulo, ajustar los pocos campos "unknown" que los datos no pudieron describir, y usar.

Preguntas frecuentes

¿Se envía mi JSON a un servidor?
No. El documento se analiza y los tipos se infieren por completo en tu navegador, y nada de lo que pegues se sube ni se registra. Es seguro usarlo con una respuesta de API real o un archivo de configuración.
¿Por qué un campo es opcional si está presente en mi muestra?
Porque está ausente de al menos un elemento de un array que la herramienta fusionó. La inferencia lee todos los elementos, no solo el primero, así que una clave que algunos registros omiten se vuelve opcional: es el tipo que tus datos realmente admiten, aunque el registro que miraste la incluyera por casualidad.
¿Por qué un campo se volvió una unión como string | number?
Porque el valor tenía tipos distintos en distintos elementos del array: una cadena en un registro y un número en otro. El tipo fusionado debe aceptar ambos, así que se escribe como una unión. Si te sorprende, suele significar que los datos son menos uniformes de lo esperado, lo cual conviene saber.
¿Por qué la herramienta usa unknown en vez de any?
Para valores que no puede describir —un array vacío, un objeto vacío— "unknown" mantiene el resultado seguro en tipos, obligando al consumidor a estrechar el valor antes de usarlo, mientras que "any" apagaría la comprobación de tipos. Puedes ajustar esos campos a mano en cuanto sepas qué contiene la colección vacía.
¿Cuál es la diferencia entre la salida interface y type?
Ninguna en los tipos que describen: ambas producen las mismas formas. "interface" es el modismo común para tipos de objeto y puede extenderse y fusionarse; los alias "type" son lo que algunas bases de código prefieren usar en todo momento. El interruptor está para que la salida encaje con el estilo de tu proyecto.
¿Puede convertir TypeScript de vuelta a JSON?
No, y a propósito. La conversión es de un solo sentido: JSON entra, tipos salen. Ir en la otra dirección supondría inventar valores que nunca estuvieron en tus datos, y todo el sentido es que cada declaración se deriva de lo que realmente pegaste.
¿Cómo se nombran los objetos anidados?
Por la clave bajo la que están: un objeto "user" se vuelve User, un "address" dentro de él se vuelve Address. Los elementos de un array se ponen en singular cuando se puede —"categories" da Category— y una clave que no se pluraliza recibe el sufijo Item. Dos formas distintas que colisionarían en un nombre reciben un sufijo en vez de fusionarse, así que siguen siendo distintas.