El paquete encoding/json de Go convierte entre valores Go y JSON de forma bidireccional. json.Marshal serializa un valor Go a JSON y json.Unmarshal deserializa JSON a un valor Go. Los struct tags controlan cómo se mapean los campos, qué claves se usan en el JSON y cuándo se omiten valores.
Marshal: de Go a JSON
package main
import (
"encoding/json"
"fmt"
)
type Persona struct {
Nombre string `json:"nombre"`
Edad int `json:"edad"`
Email string `json:"email"`
}
func main() {
p := Persona{Nombre: "Ana", Edad: 30, Email: "[email protected]"}
datos, err := json.Marshal(p)
if err != nil {
panic(err)
}
fmt.Println(string(datos))
// {"nombre":"Ana","edad":30,"email":"[email protected]"}
// con indentado para debug
bonito, _ := json.MarshalIndent(p, "", " ")
fmt.Println(string(bonito))
}
Unmarshal: de JSON a Go
jsonStr := `{"nombre":"Luis","edad":25,"email":"[email protected]"}`
var p Persona
if err := json.Unmarshal([]byte(jsonStr), &p); err != nil {
panic(err)
}
fmt.Println(p.Nombre) // Luis
fmt.Println(p.Edad) // 25
Struct tags: opciones importantes
type Producto struct {
ID int `json:"id"`
Nombre string `json:"nombre"`
Precio float64 `json:"precio,omitempty"` // omite si es 0
Descripcion string `json:"descripcion,omitempty"` // omite si es ""
Interno string `json:"-"` // nunca se serializa
SinTag string // usa el nombre del campo: "SinTag"
}
Las opciones más comunes en el tag son:
json:"nombre" renombra el campo en el JSONjson:"nombre,omitempty" omite el campo si tiene su zero valuejson:"-" excluye el campo siempre
json.Decoder para peticiones HTTP
Para leer JSON de un flujo (body de una petición HTTP, fichero...) usa json.NewDecoder en vez de leer todo en memoria con Unmarshal:
func handler(w http.ResponseWriter, r *http.Request) {
var p Persona
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields() // rechaza claves desconocidas
if err := decoder.Decode(&p); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// usar p...
}
Tipos anidados
type Direccion struct {
Calle string `json:"calle"`
Ciudad string `json:"ciudad"`
CodigoP string `json:"codigo_postal"`
}
type Cliente struct {
Nombre string `json:"nombre"`
Direccion Direccion `json:"direccion"` // struct anidado
Tags []string `json:"tags"` // array JSON
}
Tipos personalizados con MarshalJSON
Implementa MarshalJSON y UnmarshalJSON para controlar exactamente cómo se representa un tipo:
type Precio struct {
Centimos int
}
func (p Precio) MarshalJSON() ([]byte, error) {
return json.Marshal(float64(p.Centimos) / 100)
}
func (p *Precio) UnmarshalJSON(data []byte) error {
var f float64
if err := json.Unmarshal(data, &f); err != nil {
return err
}
p.Centimos = int(f * 100)
return nil
}
// JSON: 9.99 ? Go: Precio{Centimos: 999}
map string interface para claves desconocidas
Cuando no conoces la estructura del JSON de antemano:
jsonStr := `{"nombre":"Ana","activo":true,"puntuacion":9.5}`
var datos map[string]any
json.Unmarshal([]byte(jsonStr), &datos)
fmt.Println(datos["nombre"]) // Ana
fmt.Println(datos["activo"]) // true
fmt.Println(datos["puntuacion"]) // 9.5
Recuerda que los números en JSON llegan como float64 cuando el destino es any, no como int. Si necesitas enteros, convierte explícitamente o usa json.Number.
