Serialización básica: json.Marshal
json.Marshal convierte un valor Go a JSON:

type Producto struct {
ID int `json:"id"`
Nombre string `json:"nombre"`
Precio float64 `json:"precio"`
}
p := Producto{ID: 1, Nombre: "Teclado", Precio: 59.99}
datos, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(datos))
// {"id":1,"nombre":"Teclado","precio":59.99}
// Formato legible (pretty print)
datosFormateados, _ := json.MarshalIndent(p, "", " ")
fmt.Println(string(datosFormateados))
Etiquetas de struct
Las etiquetas json: controlan cómo se serializa cada campo:
type Usuario struct {
ID int `json:"id"`
Nombre string `json:"nombre"`
Email string `json:"email"`
Contraseña string `json:"-"` // nunca serializar
Biografia string `json:"biografia,omitempty"` // omitir si vacío
Edad int `json:"edad,omitempty"` // omitir si 0
FechaCreacion time.Time `json:"fecha_creacion"`
}
| Etiqueta | Efecto |
|----------|--------|
| json:"nombre" | Usa "nombre" como clave JSON |
| json:"-" | Nunca incluir en JSON |
| json:",omitempty" | Omitir si el valor es el zero value |
| json:",string" | Codificar el número como cadena |
Deserialización: json.Unmarshal
jsonData := []byte(`{"id":2,"nombre":"Ratón","precio":25.50}`)
var p Producto
if err := json.Unmarshal(jsonData, &p); err != nil {
log.Fatalf("Error al deserializar: %v", err)
}
fmt.Printf("ID: %d, Nombre: %s, Precio: %.2f\n", p.ID, p.Nombre, p.Precio)
Los campos desconocidos se ignoran por defecto, y los campos ausentes mantienen el zero value del tipo.
Streams: json.Encoder y json.Decoder
Para procesar JSON desde/hacia streams (ficheros, HTTP, etc.) sin cargar todo en memoria:
// Codificar al stream
func servirProductos(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
productos := obtenerProductos()
if err := json.NewEncoder(w).Encode(productos); err != nil {
log.Println("Error al codificar:", err)
}
}
// Decodificar desde el stream
func recibirProducto(r *http.Request) (*Producto, error) {
var p Producto
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields() // error si hay campos inesperados
if err := decoder.Decode(&p); err != nil {
return nil, fmt.Errorf("JSON inválido: %w", err)
}
return &p, nil
}
Campos opcionales con punteros
Usar punteros permite distinguir entre "campo ausente" y "campo con valor cero":
type Configuracion struct {
Host string `json:"host"`
Puerto *int `json:"puerto,omitempty"` // nil si no viene en el JSON
TLS *bool `json:"tls,omitempty"`
}
// Verificar si el campo vino en el JSON
conf := &Configuracion{}
json.Unmarshal(datos, conf)
if conf.Puerto == nil {
fmt.Println("Puerto no especificado, usando 8080")
} else {
fmt.Println("Puerto:", *conf.Puerto)
}
Fechas y tiempo
time.Time se serializa automáticamente en formato RFC 3339:
type Evento struct {
Nombre string `json:"nombre"`
Fecha time.Time `json:"fecha"`
}
e := Evento{Nombre: "Lanzamiento", Fecha: time.Now()}
datos, _ := json.Marshal(e)
// {"nombre":"Lanzamiento","fecha":"2026-03-30T10:00:00Z"}
JSON dinámico con map y interface{}
Para JSON con estructura variable:
// Deserializar JSON de estructura desconocida
var resultado map[string]any
json.Unmarshal(datos, &resultado)
// Acceder a campos
if nombre, ok := resultado["nombre"].(string); ok {
fmt.Println("Nombre:", nombre)
}
// JSON anidado
var anidado map[string]map[string]any
json.Unmarshal(datosAnidados, &anidado)
Marshaling personalizado
Implementa json.Marshaler y json.Unmarshaler para control total:
type Temperatura struct {
Celsius float64
}
func (t Temperatura) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"celsius": t.Celsius,
"fahrenheit": t.Celsius*9/5 + 32,
})
}
func (t *Temperatura) UnmarshalJSON(datos []byte) error {
var v struct {
Celsius float64 `json:"celsius"`
}
if err := json.Unmarshal(datos, &v); err != nil {
return err
}
t.Celsius = v.Celsius
return nil
}
Validación de JSON entrante
func procesarPeticion(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Content-Type") != "application/json" {
http.Error(w, "Content-Type debe ser application/json",
http.StatusUnsupportedMediaType)
return
}
r.Body = http.MaxBytesReader(w, r.Body, 1_048_576) // límite de 1 MB
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
var p Producto
if err := decoder.Decode(&p); err != nil {
var syntaxErr *json.SyntaxError
if errors.As(err, &syntaxErr) {
http.Error(w, "JSON malformado", http.StatusBadRequest)
return
}
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Procesar p...
}
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Go es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Go
Explora más contenido relacionado con Go y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Serializar structs a JSON con json.Marshal y json.MarshalIndent.
- Deserializar JSON en structs con json.Unmarshal.
- Usar etiquetas json: para controlar nombres de campos, omitempty y -.
- Codificar y decodificar JSON en streams con json.Encoder y json.Decoder.
- Manejar tipos especiales como fechas, números y campos opcionales con punteros.
- Implementar json.Marshaler y json.Unmarshaler para serialización personalizada.