Saltar al contenido principal

Convertir JSON a Go

JSON a Go genera structs a partir de un documento de ejemplo, en tu navegador. Cada campo es exportado y lleva una etiqueta json explícita, porque encoding/json ignora en silencio los campos no exportados — un nombre de campo en minúsculas es la razón más común de que un struct generado se serialice como un objeto vacío.

Solo local
JSON de ejemplo
0 B1 líneaLín. 1, Col. 1
Error de sintaxis · Error en la línea 1, columna 1

El documento termina antes de tiempo; parece incompleto.


^
Código generadosolo lectura
0 B1 línea
Si tienes varios registros, pega más de uno. Así se detectan campos opcionales que no aparecen en todas las filas.

¿Cómo funciona Convertir JSON a Go?

Tres decisiones hacen que un struct de Go generado sea correcto o esté roto en silencio, y las tres tratan de lo que encoding/json hace en tiempo de ejecución, no del aspecto del código.

Cada campo está exportado y etiquetado

Go solo serializa los campos exportados. Un struct con un campo llamado id produce {} a partir de json.Marshal y deja id en cero tras json.Unmarshal, sin error en ningún caso. Por eso cada nombre va en mayúsculas y recibe una etiqueta json:"…" explícita con la clave original. Las siglas siguen la propia guía de estilo de Go: user_id se convierte en UserID, no en UserId, y api_url se convierte en APIURL.

Los campos opcionales son punteros

Un string normal no puede contener un null de JSON, y no puede distinguir una clave ausente de una vacía — ambas llegan como "". Un int normal no puede distinguir un count ausente de un count en cero. Por eso los campos opcionales y que admiten null se convierten en punteros, la única construcción en Go que conserva la distinción. Los slices, los maps y interface{} ya admiten nil, así que se dejan como están en vez de darles una segunda indirección sin sentido.

Los enteros que int no puede contener se convierten en int64

int es de 32 bits en algunas plataformas. Un ID snowflake o una marca de tiempo en milisegundos lo desborda, y el bug resultante aparece solo en la plataforma donde es estrecho. Los valores por encima de 2,147,483,647 se emiten como int64.

Las uniones son interface{}, porque Go no tiene tipos suma

Un array que contiene tanto strings como números no tiene ningún tipo de Go que lo describa. Los generadores que inventan uno producen código que falla al deserializar; interface{} es la respuesta honesta, y el lugar para arreglarlo es la API, no el struct.

La salida ya está alineada según gofmt

Nombres, tipos, etiquetas y comentarios finales se rellenan con espacios hasta columnas coincidentes. Sin eso, el primer gofmt en CI produce un diff en un archivo que nadie editó.

Ejemplo

[
  { "user_id": 900719925474099, "api_url": "https://x.dev" },
  { "user_id": 12, "api_url": "https://y.dev", "note": "hi" }
]

Generado

type User struct {
	UserID int64   `json:"user_id"`
	APIURL string  `json:"api_url"`
	Note   *string `json:"note,omitempty"` // optional
}

¿Qué opciones y casos límite admite Convertir JSON a Go?

Referencia técnica y opciones
ParámetroTipoValor predeterminadoComportamiento y casos límite
PaqueteidentificadormainLa cláusula package en la parte superior del archivo. Establécela en el paquete donde reside el archivo de destino — models, api, internal/store.
punterosbooleanoonLos campos opcionales y nulos se convierten en *T, para que una clave ausente sea distinguible del valor cero. Desactivar esto hace que la struct sea más simple y hace que los datos faltantes sean invisibles.
omitemptybooleanoonAñade ,omitempty a los campos opcionales para que desaparezcan cuando se vuelvan a serializar. Ten en cuenta que omitempty también elimina una cadena vacía genuina o un cero, y es por eso que el puntero importa.
json.NumberbooleanooffEmite json.Number para números no enteros en lugar de float64, preservando los dígitos literales. Vale la pena para dinero o para IDs que llegan como números sin comillas más allá del rango exacto de float64.
stringtipo de GostringLos formatos detectados (uuid, date-time, email, uri) se anotan en un comentario en lugar de mapearse a un tipo, porque encoding/json no analizará una marca de tiempo en time.Time sin un unmarshaller personalizado.
integertipo de Goint / int64int por defecto; int64 cuando cualquier ejemplo supera 2,147,483,647. Un ID truncado es un error que solo aparece en producción.
numbertipo de Gofloat64Cualquier valor con parte fraccionaria. Si un entero y un flotante aparecen en el mismo campo a través de registros, el campo se convierte en float64.
objeto anidadotipo de Go*NamedSe promueve a su propia struct y se referencia por puntero, lo que evita la copia en la asignación y permite que el campo sea nulo. Las formas idénticas comparten una struct.

Preguntas frecuentes

¿Por qué cada campo va en mayúsculas si mis claves JSON van en minúsculas?

Porque encoding/json solo ve los campos exportados, y exportar en Go significa empezar con mayúscula. Un campo en minúsculas se salta tanto Marshal como Unmarshal sin dar error, así que el struct compila, se ejecuta y no hace nada. La etiqueta json lleva la clave original, así que el formato en la red no cambia.

¿Necesito los punteros?

Los necesitas allí donde ausente y cero significan cosas distintas. Un *int que es nil significa que la clave faltaba; un int que es 0 podría ser cualquiera de las dos cosas. Para un campo como retry_count o discount esa distinción normalmente importa. Para un campo que sabes que siempre está presente es ruido, y desactivar los punteros es razonable.

¿Por qué mi marca de tiempo es un string en lugar de time.Time?

Porque encoding/json no interpreta por su cuenta una marca de tiempo arbitraria como time.Time — solo maneja RFC 3339, y solo si el campo está declarado como time.Time, que entonces falla de forma rotunda con cualquier otro formato. Generar time.Time a partir del ejemplo produciría un struct que entra en pánico con el primer registro que formatee una fecha de otra manera. El formato detectado se indica en un comentario para que puedas tomar esa decisión a conciencia.

¿Qué hace un interface{} en mi struct?

Un campo contenía tipos realmente distintos entre tus ejemplos — un string en un registro, un objeto en otro — y Go no tiene un tipo que lo exprese. Las alternativas son un UnmarshalJSON personalizado, un json.RawMessage que descodificas después, o arreglar la API. Un tipo unión generado simplemente fallaría al deserializar.

¿gofmt cambiará la salida?

No debería. Los nombres de campo, los tipos, las etiquetas y los comentarios finales se rellenan hasta columnas coincidentes, que es justo lo que gofmt hace con un struct. Si encuentras un caso en el que gofmt aún produce un diff, es un bug que merece reportarse, no una preferencia de estilo.

¿Se sube mi JSON?

No. El análisis, la inferencia de tipos y el renderizado se ejecutan todos en tu navegador. No se envía nada a ningún sitio, lo cual importa porque los ejemplos que la gente pega en un generador de structs suelen ser respuestas reales de un sistema con el que se están integrando.