Escribir a mano las clases de modelo de cada respuesta de una API es lento y fácil de equivocar. Generarlas a partir de un ejemplo real te lleva la mayor parte del camino, pero cada lenguaje tiene sus convenciones, y conviene saber qué significa el código generado.
Los mismos datos en cuatro lenguajes
Toma una carga con un id, un nombre, un cupón opcional y una dirección anidada. El generador infiere los tipos a partir de los valores: los números enteros pasan a enteros, los decimales a flotantes, y los campos que faltan en algunos elementos de un arreglo pasan a opcionales.
Python: dataclasses
Python recibe un @dataclass por objeto con anotaciones de tipo como str, int, list[Order] y Optional[str]. Los campos sin valor por defecto van primero, porque una dataclass no puede tener un campo obligatorio después de uno con valor por defecto. Los campos opcionales y los que admiten nulo toman None por defecto. Las claves que no son nombres válidos de Python, como first-name o class, se convierten (first_name, class_), así que haz tú el mapeo si cargas los datos con diccionarios simples.
Java: records
Los records (Java 16 y posteriores) dan clases inmutables con casi nada de código. Se usan tipos primitivos para los campos obligatorios y tipos envueltos (Integer, Double) para los opcionales, ya que los primitivos no pueden ser nulos. Cuando el nombre de un campo difiere de la clave JSON, una anotación @JsonProperty de Jackson mantiene la correspondencia.
C#: clases con propiedades
Cada objeto se convierte en una clase con propiedades { get; set; } en PascalCase. Un atributo [JsonPropertyName] de System.Text.Json guarda la clave original, lo que importa porque ese serializador distingue mayúsculas de forma predeterminada. Los campos opcionales usan tipos que admiten nulo como int? y string?.
Rust: structs con serde
Los structs de Rust derivan Serialize y Deserialize. Los campos opcionales y los que admiten nulo pasan a Option<T>, los enteros pasan a i32 o i64 según el tamaño, y las claves se convierten a snake_case con un atributo #[serde(rename = "...")]. Las palabras reservadas de Rust como type usan la forma de identificador crudo r#type.
Lo que un ejemplo no puede decirte
Un solo ejemplo no puede mostrar todas las variaciones. Un campo que en tu ejemplo es siempre un entero puede ser decimal en otro caso, y una enumeración parece una cadena normal. Revisa el resultado con la documentación de la API y usa un ejemplo con varios elementos para que aparezcan los campos opcionales.
Preguntas frecuentes
¿Por qué algunos números son long o i64? Los valores mayores que un entero de 32 bits necesitan un tipo más ancho.
¿La herramienta sube mi JSON? No, la generación se ejecuta en tu navegador.

