JSON a tipos: genera interfaces y clases de TypeScript, C#, Java, Go, Python y Rust

Pega la respuesta de una API o cualquier JSON de ejemplo y obtén sus tipos listos para copiar: interfaces de TypeScript, records o clases de C#, records de Java, structs de Go y de Rust, y dataclasses o TypedDict de Python. La herramienta recorre todo el ejemplo: si un campo falta en algún elemento de una lista lo marca como opcional, si vale null lo marca como anulable, y reutiliza el mismo tipo cuando dos objetos tienen la misma forma. Todo se calcula en tu navegador: el JSON no se envía a ningún lado.

Ejemplos:

Un ejemplo no es un contrato. La herramienta deduce los tipos de lo que ve: si en tu ejemplo un precio siempre es entero, saldrá como entero, y si una lista llega vacía no puede saber qué contiene. Usa un ejemplo lo más completo posible, con varios elementos en las listas, y revisa los avisos. Si la API tiene un esquema oficial (OpenAPI o JSON Schema), genera los tipos a partir de él.

Cómo deduce los tipos

  • Números: si todos los valores de un campo son enteros, el tipo es entero (int, o long e int64 si alguno no cabe en 32 bits); si alguno tiene decimales, es double o float64. En TypeScript todo es number y en Rust se usan i64 y f64.
  • Listas de objetos: se juntan los campos de todos los elementos. Un campo que aparece en unos sí y en otros no es opcional: campo? en TypeScript, un tipo con ? en C#, un puntero con omitempty en Go, Option en Rust o = None en Python.
  • null: un campo que vale null en algún elemento es anulable. Si en el ejemplo solo vale null, no hay forma de saber su tipo, y sale como genérico (unknown, object?, any o serde_json::Value).
  • Objetos anidados: cada uno es un tipo con el nombre de su clave en PascalCase; los de una lista toman el singular (pedidos → Pedido, ciudades → Ciudad). Si dos objetos tienen exactamente los mismos campos, se usa un único tipo.
  • Nombres: cada lenguaje recibe su estilo (nombreCompleto en Java, NombreCompleto en C# y Go, nombre_completo en Python y Rust) y la clave original se conserva con [JsonPropertyName], @JsonProperty, la etiqueta json:"…" de Go o #[serde(rename)]. Los acentos se quitan, la ñ pasa a ni (año → anio) y las palabras reservadas se escapan (type → r#type en Rust, class_ en Java y Python, @class en C#).

Así queda el mismo campo opcional y anulable en cada lenguaje:

TypeScript   cupon?: string | null;
C#           [property: JsonPropertyName("cupon")] string? Cupon
Java         @JsonProperty("cupon") String cupon
Go           Cupon *string `json:"cupon,omitempty"`
Python       cupon: str | None = None
Rust         #[serde(default, skip_serializing_if = "Option::is_none")]
             pub cupon: Option<String>,

Qué necesita cada lenguaje

  • TypeScript: nada; las interfaces solo existen al compilar. Si quieres validar en ejecución que el JSON cumple el tipo, usa una biblioteca como Zod. Más en nuestra sección de TypeScript.
  • C#: System.Text.Json, incluido en .NET. Los record posicionales necesitan C# 9; la clase con propiedades funciona en cualquier versión. Más en la sección de C#.
  • Java: Jackson 2.12 o posterior, que entiende los record de Java 16. Cada record público va en su propio archivo; los demás pueden quedarse juntos sin public. Más en la sección de Java.
  • Go: solo encoding/json. Los punteros distinguen un campo que falta o es null de uno que vale cero o está vacío. Más en la sección de Go.
  • Python: las dataclasses traen from_dict y to_dict para pasar de y a JSON con las claves originales; los TypedDict solo los usa el comprobador de tipos. Más en la sección de Python.
  • Rust: serde con la función derive y serde_json. Más en la sección de Rust.

Leer JSON con tipos en Python

Una dataclass como las que genera la herramienta, usada con el módulo json de la biblioteca estándar:

from __future__ import annotations

import json
from dataclasses import dataclass, asdict
from typing import Any


# Lo que genera la herramienta para {"id": 10, "total": 299.9, "cupon": "OTONO"}, si "cupon" falta en algún pedido
@dataclass
class Pedido:
    id: int
    total: float
    cupon: str | None = None          # opcional: va al final y con valor por defecto

    @classmethod
    def from_dict(cls, d: dict[str, Any]) -> Pedido:
        return cls(id=d["id"], total=float(d["total"]), cupon=d.get("cupon"))


texto = '[{"id": 10, "total": 299.9, "cupon": "OTONO"}, {"id": 11, "total": 50}]'
pedidos = [Pedido.from_dict(p) for p in json.loads(texto)]
print(pedidos[1])                       # el 50 llega como int y float() lo deja en 50.0
print(sum(p.total for p in pedidos))
print(json.dumps([asdict(p) for p in pedidos]))   # asdict sirve si los nombres coinciden con las claves

try:
    Pedido.from_dict({"total": 5})
except KeyError as falta:                # un campo obligatorio que no viene
    print("Falta el campo", falta)
$ python3 json_a_dataclass.pyPedido(id=11, total=50.0, cupon=None)
349.9
[{"id": 10, "total": 299.9, "cupon": "OTONO"}, {"id": 11, "total": 50.0, "cupon": null}]
Falta el campo 'id'

Otras herramientas para desarrolladores: formateador de JSON, conversor entre JSON, YAML, TOML y CSV, decodificador de JWT y generador de UUID. Todas, en decodigo.com/herramientas.

Antes de publicarla, el código que genera para 60 JSON de ejemplo (respuestas de API, GeoJSON, claves con espacios, acentos, palabras reservadas, listas vacías, valores mixtos y null) se compiló con TypeScript 5.6 en modo --strict, .NET 8 con los avisos tratados como errores, Java 21 con Jackson 2.18, Go 1.23, Rust 1.82 con serde y mypy en modo estricto. En los seis lenguajes, cada ejemplo se lee con los tipos generados y se vuelve a escribir igual que el original.