Convertir JSON a una estructura de Go significa transformar una carga útil JSON de ejemplo en un tipo de Go con los tipos de campo correctos, nombres exportados y etiquetas json: para poder hacer Unmarshal de forma limpia. Un generador hace esto inspeccionando el valor de cada clave e infiriendo un tipo de Go, luego asigna el nombre de la clave JSON a un campo exportado en PascalCase con una etiqueta coincidente. Esta guía muestra exactamente cómo funciona esa inferencia, dónde falla y cómo obtener una estructura correcta sin escribir manualmente una sola llave.
La mayoría de los desarrolladores se topan con el mismo problema: una API devuelve un objeto JSON de 40 claves, y escribir la estructura correspondiente a mano es lento y propenso a errores. Olvidas una etiqueta, adivinas int donde la API envía un float, o aplanas un objeto anidado que debería ser su propio tipo. El resultado es un Unmarshal en tiempo de ejecución que descarta campos silenciosamente. La solución es entender las reglas que aplica un conversor, para poder confiar en su salida y corregir los casos que no puede adivinar.
Cómo un Conversor de JSON a Struct de Go Infiere Tipos#
Un conversor recorre el JSON valor por valor. JSON solo tiene seis tipos de valor (cadena, número, booleano, objeto, arreglo, nulo), pero Go tiene muchos. El mapeo es mayormente determinista, con dos casos realmente ambiguos que debes conocer.
Aquí está el mapeo central que usa todo generador:
| Valor JSON | Tipo Go inferido | Notas |
|---|---|---|
"hola" | string | Siempre inequívoco |
true / false | bool | Siempre inequívoco |
42 | int | Números enteros por defecto a int |
3.14 | float64 | Cualquier decimal se convierte en float64 |
{ ... } | struct anidado | Se convierte en su propio tipo nombrado |
[ ... ] | []T | Tipo de elemento inferido de los ítems |
null | interface{} | No hay información de tipo disponible |
Los dos casos que merecen tu atención son números y nulo. Todo lo demás el conversor lo hace bien siempre.
Por qué int vs float64 es la decisión más complicada#
JSON no tiene un tipo entero. El número 5 y el número 5.0 son solo "número" para un analizador JSON. Un generador debe adivinar: si el literal no tiene punto decimal, elige int; si ve un punto decimal, elige float64.
Esa heurística falla de una manera común. Imagina un campo de API precio que resulta ser 100 en tu muestra pero normalmente es 100.50. El conversor ve un número entero y escribe int. La siguiente respuesta con 100.50 entonces falla al deserializar en un int, o peor, trunca.
Siempre verifica los campos numéricos contra la documentación de la API, no solo contra tu muestra. Si un campo puede contener una fracción, cambia el
intgenerado afloat64manualmente. Una muestra limpia no es un esquema.
También existe la opción json.Number. Si la precisión importa (IDs grandes, dinero, cualquier cosa que no puedas redondear), decodifica en json.Number o una cadena y analiza deliberadamente. Los generadores por defecto usan float64 para decimales, lo cual está bien para la mayoría de datos, pero es impreciso para IDs enteros de 64 bits más allá de 2^53.
Cuando obtienes un tipo de interfaz vacía#
Si un valor JSON es null, el conversor no tiene nada de qué inferir. No hay tipo, solo la ausencia de uno. Así que recurre a interface{} (escrito any en Go moderno).
La misma recurrencia ocurre para arreglos de tipo mixto. Un arreglo JSON como [1, "dos", true] no tiene un solo tipo de elemento, por lo que el generador produce []interface{}. Eso es técnicamente correcto pero horrible de trabajar, porque pierdes toda seguridad de tipo en tiempo de compilación y debes afirmar el tipo de cada elemento.
Cuando veas interface{} en la salida generada, trátalo como una bandera que dice "No pude resolver esto, tú decides". Normalmente lo correcto es darle al conversor una muestra más rica donde ese campo esté poblado, y luego regenerar.
Etiquetas de campo, nombres exportados y omitempty#
Acertar con los tipos es la mitad del trabajo. La otra mitad son las etiquetas de estructura json: y la nomenclatura de campos, que es donde fallan más a menudo las estructuras escritas a mano.
Por qué los campos deben ser exportados (con mayúscula inicial)#
El paquete encoding/json de Go solo puede leer y escribir campos de estructura exportados, es decir, campos cuyo nombre comienza con mayúscula. Un campo no exportado (en minúscula) es invisible para Unmarshal y Marshal. Se quedará silenciosamente en su valor cero sin ningún error.
Por lo tanto, una clave JSON user_name no puede asignarse a un campo Go llamado user_name. El convertidor lo renombra al exportado UserName y añade una etiqueta para salvar la distancia:
type User struct {
UserName string `json:"user_name"`
Email string `json:"email"`
}
La etiqueta json:"user_name" le dice al paquete JSON: este campo exportado UserName corresponde a la clave JSON user_name. Sin esa etiqueta, Go buscaría una clave llamada UserName (hace una coincidencia sin distinción de mayúsculas como alternativa, pero confiar en eso es frágil). La conversión de snake_case o camelCase a PascalCase más una etiqueta explícita es exactamente lo que un buen generador automatiza.
Qué cambia realmente omitempty#
La opción omitempty aparece en etiquetas como json:"email,omitempty". Solo afecta a la serialización (de estructura Go a JSON), nunca a la deserialización. Cuando codificas una estructura, omitempty le dice al codificador que omita un campo por completo si su valor es el valor cero (cadena vacía, 0, false, nil, slice o mapa vacío).
Esto importa para la salida de una API. Compara la misma estructura con y sin ella:
| Etiqueta | Valor del campo | Salida JSON |
|---|---|---|
json:"middle_name" | "" (vacío) | "middle_name": "" |
json:"middle_name,omitempty" | "" (vacío) | (campo omitido) |
json:"age,omitempty" | 0 | (campo omitido) |
La trampa con omitempty es que no puede distinguir "cero intencional" de "no establecido". Si age es legítimamente 0 (un recién nacido), omitempty lo elimina de la salida. Cuando necesites distinguir "ausente" de "cero", usa un campo puntero (*int) en su lugar, donde nil significa ausente y &zero significa un cero real.
La mayoría de los convertidores no añaden
omitemptypor defecto, porque es una elección de serialización, no un hecho del esquema. Añádelo deliberadamente a campos que sean realmente opcionales en tu salida, y recurre a campos puntero cuando el valor cero sea significativo.
Manejo de Estructuras Anidadas y Repetidas#
Las respuestas reales de una API rara vez son planas. Anidan objetos dentro de objetos y contienen arreglos de objetos. Aquí es donde escribir estructuras manualmente se vuelve realmente tedioso y donde un conversor demuestra su valor.
Cuando un conversor encuentra un objeto anidado, tiene dos estrategias:
- Estructura en línea (anónima): el tipo anidado se escribe en línea dentro de su padre. Compacto, pero no puedes referenciar el tipo interno en otro lugar.
- Tipos nombrados (extraídos): cada objeto anidado se convierte en su propia
structcon nombre, referenciada por el padre. Más verboso, pero reutilizable y mucho más fácil de leer.
Para cualquier carga útil que no sea trivial, prefiere tipos nombrados. Dado este JSON:
{
"id": 7,
"owner": { "name": "Ada", "verified": true },
"tags": ["go", "json"]
}
Un conversor que usa tipos nombrados produce:
type Repo struct {
ID int `json:"id"`
Owner Owner `json:"owner"`
Tags []string `json:"tags"`
}
type Owner struct {
Name string `json:"name"`
Verified bool `json:"verified"`
}
Observa que tags se convirtió en []string porque cada elemento era una cadena, y owner se convirtió en su propio tipo Owner. Para un arreglo de objetos ("items": [ {...}, {...} ]), el conversor inspecciona el primer elemento (o combina entre elementos si es inteligente) y produce []Item con una estructura Item generada.
El caso límite de combinación de arreglos#
Aquí hay un detalle que los generadores más baratos pasan por alto. Si un arreglo contiene objetos donde algunos elementos tienen un campo y otros no, un conversor ingenuo solo lee el primer elemento y se pierde campos que aparecen más tarde. Un generador robusto combina claves de todos los elementos del arreglo para que la estructura capture cada campo, marcando aquellos que a veces están ausentes como candidatos para punteros u omitempty. Si tu estructura generada está omitiendo campos que existen más adelante en una lista, esa es la causa.
Generar una estructura de Go a partir de JSON, paso a paso#
Puedes hacerlo en tu navegador en menos de un minuto, sin necesidad de la cadena de herramientas de Go ni de go install. El formateador y conversor de JSON gratuito de Molixa primero valida tu JSON y luego genera una estructura de Go (junto con TypeScript, Zod y otros destinos) para que detectes problemas de sintaxis antes de que se conviertan en errores de compilación de Go.
Paso 1: Pega y valida tu JSON#
Coloca una respuesta representativa en el editor. La palabra más importante aquí es representativa: usa un ejemplo con todos los campos completados y valores realistas, no uno simplificado. Las muestras vacías o con muchos valores nulos son exactamente las que producen campos interface{}. El formateador marca inmediatamente las comas finales, las comillas simples y las claves sin comillas, lo cual es mucho menos doloroso que perseguir un error de deserialización de Go más adelante.
Paso 2: Genera la estructura de Go#
Cambia la salida al destino de estructura de Go. El conversor recorre tu JSON, deduce cada tipo, exporta y renombra los campos a PascalCase, y escribe las etiquetas json: correspondientes. Los objetos anidados se convierten en tipos nombrados y los arrays en slices automáticamente. Copia el resultado directamente en tu paquete.
Paso 3: Corrige los campos ambiguos manualmente#
Este es el paso que los generadores no pueden hacer por ti, y saltarlo es la razón por la que la gente piensa que los conversores no son fiables. Revisa la salida en busca de tres cosas:
- Campos
intque pueden contener decimales: cámbialos afloat64(ojson.Numberpara precisión). - Campos
interface{}: provienen de valoresnullo arrays mixtos. Reemplázalos con un tipo concreto, o proporciona una muestra más rica y regenera. - IDs enteros grandes: si un ID supera 2^53, un
float64perderá precisión. Usaint64,stringojson.Numbersegún la API.
Paso 4: Añade omitempty y punteros donde corresponda#
Decide, por cada campo, si es realmente opcional en la salida. Añade ,omitempty a los campos que deberían desaparecer cuando están vacíos. Para campos donde un valor cero es significativo (un 0 real, un false real), cambia a un tipo puntero para que nil indique claramente "no proporcionado". Luego escribe una pequeña prueba Unmarshal con tu muestra real para confirmar que no se descarta nada silenciosamente.
Confiar en el resultado: verifique antes de enviar#
Una estructura generada es un primer borrador sólido, no una garantía. El flujo de trabajo honesto es: generar, luego demostrar que funciona con sus datos reales. Deserialice una respuesta real en la estructura, serialícela de nuevo y compare con el original. Los campos faltantes o reordenados aparecen al instante.
Si también trabaja con TypeScript en la misma API, la misma muestra JSON puede servir para ambos. Nuestra guía sobre cómo convertir JSON a una interfaz de TypeScript cubre las reglas paralelas de inferencia de tipos en el frontend, y si está depurando el payload sin procesar primero, cómo formatear JSON en JavaScript explica el formato bonito y las opciones de JSON.stringify que hacen legible una respuesta desordenada antes de convertirla.
La razón por la que un conversor supera al tipeo manual no es solo la velocidad. Aplica la regla de exportación, el mapeo de etiquetas y el anidamiento de forma consistente cada vez, por lo que lo único que queda para usted es el pequeño conjunto de decisiones subjetivas (int vs float, opcional vs requerido, precisión) que ninguna herramienta puede inferir de una sola muestra. Esa división del trabajo es el objetivo principal.
Conclusión: De JSON sin procesar a una estructura Go confiable#
Convertir JSON a una estructura Go es mecánico para la mayoría de los campos y basado en criterio para unos pocos. Un generador maneja el 90% mecánico: exportar campos, escribir etiquetas json:, nombrar tipos anidados y convertir arreglos en slices. Tú manejas el 10% que no puede inferir, principalmente la precisión numérica, los campos interface{} derivados de null y si omitempty o un puntero corresponde en cada campo opcional.
Domina esas reglas y la conversión de JSON a estructura Go se vuelve aburrida en el mejor sentido. Pega una muestra representativa en el formateador JSON y convertidor a estructura Go, genera el tipo, corrige los pocos campos ambiguos y confirma que convierte correctamente tus datos reales. Esa es la diferencia entre una estructura que compila y una en la que realmente puedes confiar en producción.
Preguntas Frecuentes#
¿Cómo convierto JSON a una estructura de Go?
Pega una muestra representativa de JSON en un conversor, que infiere un tipo de Go para cada valor, exporta y renombra las claves a PascalCase, y agrega las etiquetas json: correspondientes. Luego revisa manualmente los campos numéricos y cualquier tipo interface{} que la herramienta no haya podido inferir. Puedes hacer todo gratis en el navegador con el formateador y conversor JSON de Molixa, sin necesidad del entorno de Go.
¿Por qué mi estructura generada tiene campos interface{}?
Un campo interface{} (o any) significa que el conversor no pudo inferir un tipo. Esto ocurre cuando el valor JSON era null, o cuando un array contenía tipos mixtos. La solución es proporcionar una muestra donde ese campo tenga un valor real, luego regenerar, o reemplazar el interface{} con el tipo concreto que sabes que debería ser.
¿Los campos de una estructura de Go deben estar en mayúscula?
Sí, si quieres que encoding/json los lea o escriba. El paquete JSON de Go solo ve campos exportados, es decir, aquellos que comienzan con mayúscula. Un campo en minúscula se ignora durante Marshal y Unmarshal sin error, por lo que permanece en su valor cero. Los conversores exportan todos los campos y usan la etiqueta json: para mapear de vuelta al nombre de clave original.
¿Qué hace omitempty en una etiqueta JSON?
La opción omitempty solo afecta la serialización (codificar una estructura a JSON). Le dice al codificador que omita un campo cuando su valor es el valor cero: cadena vacía, 0, false, nil, o un slice o mapa vacío. No tiene efecto en la deserialización. Ten cuidado, porque no puede distinguir un cero intencional de un campo no establecido, así que usa un tipo puntero cuando el valor cero sea significativo.
¿Debo usar int o float64 para números JSON en Go?
Usa float64 para cualquier campo que pueda tener decimales, y int solo para valores que siempre sean enteros. Debido a que JSON no tiene un tipo entero, los conversores adivinan a partir de tu muestra, por lo que un campo que es 100 en la muestra pero a veces 100.50 obtiene el tipo incorrecto. Para IDs grandes o dinero donde la precisión importa, prefiere int64, string o json.Number para evitar redondeos de punto flotante.
¿Cómo manejo objetos JSON anidados en una estructura de Go? Convierte cada objeto anidado en su propio tipo de estructura con nombre, referenciado por el padre, en lugar de incluir todo en línea. Esto mantiene el código legible y te permite reutilizar el tipo interno. Un buen conversor hace esto automáticamente y también maneja arrays de objetos generando un slice de una estructura con nombre, idealmente fusionando claves de todos los elementos del array para que no se pierda ningún campo.



