CAP 09 · LEC 02·Stdlib y herramientas

`encoding/json`: struct tags, `Marshal` y `Unmarshal`

JSON es el formato universal de las APIs. Go incluye `encoding/json` en la librería estándar y resuelve la conversión entre structs y JSON de forma declarativa, usando struct tags.

● INTERMEDIO7 min lecturapor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

`Marshal`: de struct a JSON

json.Marshal recibe cualquier valor de Go y devuelve sus bytes en formato JSON. Funciona con structs, slices, maps, y tipos básicos.

package main import ( "encoding/json" "fmt" ) type User struct { Name string Email string Age int } func main() { u := User{Name: "Ada", Email: "ada@example.com", Age: 36} data, err := json.Marshal(u) if err != nil { panic(err) } fmt.Println(string(data)) }
Salida{"Name":"Ada","Email":"ada@example.com","Age":36}

Por defecto, las claves del JSON son los nombres del campo en Go. Para personalizarlas, se usan struct tags.

Campos exportados solamente

encoding/json solo serializa campos exportados (los que empiezan con mayúscula). Un campo name string (minúscula) será ignorado por completo en Marshal y Unmarshal, sin error ni warning.

Struct tags: controlar la salida

Las struct tags son strings entre backticks que se anotan junto al tipo del campo. La librería las lee por reflexión.

package main import ( "encoding/json" "fmt" ) type User struct { Name string `json:"name"` Email string `json:"email"` Age int `json:"age,omitempty"` Password string `json:"-"` } func main() { u := User{ Name: "Ada", Email: "ada@example.com", Password: "secret123", // Age omitido → vale 0 } data, _ := json.Marshal(u) fmt.Println(string(data)) }
Salida{"name":"Ada","email":"ada@example.com"}

Las tres tags más usadas:

  • json:"name" — renombra la clave en el JSON.
  • json:",omitempty" — omite el campo si su valor es el zero value del tipo (0, "", nil, false).
  • json:"-" — ignora el campo en ambas direcciones. Útil para passwords, tokens y campos internos.
Combinando tags

Se pueden combinar nombre y opciones: json:"age,omitempty" renombra a age y omite si es cero. La coma sin nombre (json:",omitempty") mantiene el nombre del campo Go.

Salida formateada con `MarshalIndent`

Para JSON legible (logs, debugging, archivos de configuración), usa MarshalIndent:

package main import ( "encoding/json" "fmt" ) type Product struct { ID int `json:"id"` Name string `json:"name"` Price float64 `json:"price"` } func main() { p := Product{ID: 1, Name: "Teclado", Price: 49.99} // prefix = "", indent = " " (dos espacios) data, _ := json.MarshalIndent(p, "", " ") fmt.Println(string(data)) }
Salida{ "id": 1, "name": "Teclado", "price": 49.99 }

`Unmarshal`: de JSON a struct

La operación inversa: recibe bytes JSON y los escribe en un puntero al destino.

package main import ( "encoding/json" "fmt" ) type User struct { Name string `json:"name"` Email string `json:"email"` Age int `json:"age"` } func main() { raw := []byte(`{"name":"Ada","email":"ada@example.com","age":36}`) var u User if err := json.Unmarshal(raw, &u); err != nil { panic(err) } fmt.Printf("%+v\n", u) }
Salida{Name:Ada Email:ada@example.com Age:36}

Notas importantes:

  • El segundo argumento es siempre un puntero (&u). Sin él, Unmarshal no puede modificar el valor.
  • Campos del JSON que no existen en la struct se ignoran silenciosamente.
  • Campos de la struct que no aparecen en el JSON quedan con su zero value.

JSON dinámico: `map[string]any`

Cuando no conoces el shape del JSON de antemano (APIs flexibles, configuración variable), puedes deserializar a un map[string]any (alias de map[string]interface{} desde Go 1.18).

package main import ( "encoding/json" "fmt" ) func main() { raw := []byte(`{ "name": "Ada", "age": 36, "skills": ["go", "math"], "active": true }`) var data map[string]any if err := json.Unmarshal(raw, &data); err != nil { panic(err) } // Hay que hacer type assertion para usar los valores: name := data["name"].(string) age := data["age"].(float64) // todos los números → float64 skills := data["skills"].([]any) fmt.Println(name, int(age), skills) }
SalidaAda 36 [go math]
Números siempre float64

Al deserializar a any, todos los números se convierten a float64, incluso si en el JSON parecen enteros. Si necesitas un int, haz cast: int(data["age"].(float64)).

Para producción, prefiere siempre structs concretos: son más rápidos, type-safe y autodocumentan el contrato de tu API. map[string]any es la salida de emergencia.