← Volver al listado de tecnologías
JSON y HTTP
JSON y HTTP
Go incluye soporte completo para JSON y HTTP en su biblioteca estandar. No necesitas frameworks externos para construir APIs robustas.
encoding/json
Marshal: struct a JSON
package main
import (
"encoding/json"
"fmt"
)
type Usuario struct {
ID int `json:"id"`
Nombre string `json:"nombre"`
Email string `json:"email"`
Edad int `json:"edad,omitempty"` // Omite si es valor cero
clave string // Campo no exportado: ignorado por json
}
func main() {
u := Usuario{
ID: 1,
Nombre: "Ana",
Email: "[email protected]",
}
// Marshal compacto
datos, err := json.Marshal(u)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(string(datos))
// {"id":1,"nombre":"Ana","email":"[email protected]"}
// MarshalIndent para formato legible
bonito, _ := json.MarshalIndent(u, "", " ")
fmt.Println(string(bonito))
}
Struct tags JSON
| Tag | Efecto |
|---|---|
`json:"nombre"` | Usa “nombre” como clave JSON |
`json:"nombre,omitempty"` | Omite si el valor es cero |
`json:"-"` | Ignora el campo siempre |
`json:",string"` | Codifica numero/bool como string |
type Config struct {
Host string `json:"host"`
Puerto int `json:"puerto,string"` // "puerto": "8080"
Debug bool `json:"debug,omitempty"`
Internal string `json:"-"` // Nunca se serializa
}
Unmarshal: JSON a struct
package main
import (
"encoding/json"
"fmt"
)
type Producto struct {
Nombre string `json:"nombre"`
Precio float64 `json:"precio"`
Stock int `json:"stock"`
}
func main() {
jsonStr := `{"nombre":"Laptop","precio":999.99,"stock":50}`
var p Producto
err := json.Unmarshal([]byte(jsonStr), &p)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("%+v\n", p)
// {Nombre:Laptop Precio:999.99 Stock:50}
}
JSON dinamico con map
Cuando no conoces la estructura de antemano:
var datos map[string]any
json.Unmarshal([]byte(`{"nombre":"Go","version":1.22}`), &datos)
fmt.Println(datos["nombre"]) // Go
fmt.Println(datos["version"]) // 1.22 (float64)
json.NewEncoder y json.NewDecoder
Para trabajar con streams (archivos, HTTP bodies, conexiones):
// Encoder: escribe JSON a un Writer
enc := json.NewEncoder(os.Stdout)
enc.Encode(Evento{Tipo: "info", Mensaje: "inicio"})
// Decoder: lee JSON de un Reader
dec := json.NewDecoder(strings.NewReader(jsonStr))
var e Evento
dec.Decode(&e)
json.RawMessage
Retrasa el parsing de parte del JSON.
package main
import (
"encoding/json"
"fmt"
)
type Respuesta struct {
Tipo string `json:"tipo"`
Data json.RawMessage `json:"data"` // Se parsea despues
}
type Usuario struct {
Nombre string `json:"nombre"`
}
type Error struct {
Codigo int `json:"codigo"`
Detalle string `json:"detalle"`
}
func main() {
jsonStr := `{"tipo":"usuario","data":{"nombre":"Ana"}}`
var resp Respuesta
json.Unmarshal([]byte(jsonStr), &resp)
switch resp.Tipo {
case "usuario":
var u Usuario
json.Unmarshal(resp.Data, &u)
fmt.Println("Usuario:", u.Nombre)
case "error":
var e Error
json.Unmarshal(resp.Data, &e)
fmt.Printf("Error %d: %s\n", e.Codigo, e.Detalle)
}
}
net/http: Servidor HTTP
Servidor basico
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hola desde Go!")
})
http.HandleFunc("/salud", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"status":"ok"}`)
})
fmt.Println("Servidor en :8080")
http.ListenAndServe(":8080", nil) // nil usa DefaultServeMux
}
http.Handler interface
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Cualquier tipo que implemente ServeHTTP es un Handler.
package main
import (
"encoding/json"
"net/http"
"time"
)
type APIHandler struct{}
func (h *APIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{
"hora": time.Now().Format(time.RFC3339),
"status": "activo",
})
}
func main() {
http.Handle("/api", &APIHandler{})
http.ListenAndServe(":8080", nil)
}
ServeMux mejorado (Go 1.22+)
Go 1.22 mejoro el router con soporte para metodos HTTP y wildcards.
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Tarea struct {
ID string `json:"id"`
Titulo string `json:"titulo"`
}
var tareas = map[string]Tarea{
"1": {ID: "1", Titulo: "Aprender Go"},
"2": {ID: "2", Titulo: "Construir API"},
}
func main() {
mux := http.NewServeMux()
// Metodo + patron
mux.HandleFunc("GET /tareas", listarTareas)
mux.HandleFunc("POST /tareas", crearTarea)
// Wildcard con {nombre}
mux.HandleFunc("GET /tareas/{id}", obtenerTarea)
mux.HandleFunc("DELETE /tareas/{id}", eliminarTarea)
fmt.Println("API en :8080")
http.ListenAndServe(":8080", mux)
}
func listarTareas(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
lista := make([]Tarea, 0, len(tareas))
for _, t := range tareas {
lista = append(lista, t)
}
json.NewEncoder(w).Encode(lista)
}
func obtenerTarea(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // Go 1.22+
t, ok := tareas[id]
if !ok {
http.Error(w, "tarea no encontrada", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(t)
}
func crearTarea(w http.ResponseWriter, r *http.Request) {
var t Tarea
if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
http.Error(w, "json invalido", http.StatusBadRequest)
return
}
tareas[t.ID] = t
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(t)
}
func eliminarTarea(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
delete(tareas, id)
w.WriteHeader(http.StatusNoContent)
}
http.Request: leer datos del cliente
func handler(w http.ResponseWriter, r *http.Request) {
// Metodo HTTP
fmt.Println(r.Method) // GET, POST, etc
// Query params: /buscar?q=golang&page=1
q := r.URL.Query().Get("q")
page := r.URL.Query().Get("page")
// Headers
auth := r.Header.Get("Authorization")
contentType := r.Header.Get("Content-Type")
// Body (para POST/PUT)
defer r.Body.Close()
var datos map[string]any
json.NewDecoder(r.Body).Decode(&datos)
// Path value (Go 1.22+)
id := r.PathValue("id")
_ = q; _ = page; _ = auth; _ = contentType; _ = id
}
http.ResponseWriter: enviar respuesta
func responder(w http.ResponseWriter, r *http.Request) {
// Headers (antes de WriteHeader)
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Custom", "valor")
// Status code
w.WriteHeader(http.StatusCreated) // 201
// Body
json.NewEncoder(w).Encode(map[string]string{
"mensaje": "creado exitosamente",
})
}
| Funcion | Uso |
|---|---|
http.Error(w, msg, code) | Respuesta de error rapida |
http.NotFound(w, r) | Responde 404 |
http.Redirect(w, r, url, code) | Redireccion |
http.ServeFile(w, r, path) | Sirve archivo estatico |
http.Client: hacer requests
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
func main() {
// Cliente con timeout
client := &http.Client{Timeout: 10 * time.Second}
// GET
resp, err := client.Get("https://httpbin.org/get")
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("Status:", resp.StatusCode)
fmt.Println(string(body))
// POST con JSON
payload := map[string]string{"nombre": "Go"}
jsonBody, _ := json.Marshal(payload)
resp2, err := client.Post(
"https://httpbin.org/post",
"application/json",
bytes.NewReader(jsonBody),
)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp2.Body.Close()
fmt.Println("POST Status:", resp2.StatusCode)
}
Request personalizado
Para requests con headers custom, usa http.NewRequest + client.Do:
client := &http.Client{Timeout: 5 * time.Second}
req, err := http.NewRequest("GET", "https://api.ejemplo.com/datos", nil)
if err != nil {
fmt.Println("Error:", err)
return
}
req.Header.Set("Authorization", "Bearer mi-token")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
Middleware
Un middleware envuelve un handler para agregar funcionalidad transversal.
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inicio := time.Now()
next.ServeHTTP(w, r)
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(inicio))
})
}
func cors(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
// Encadenar: handler := logging(cors(mux))
Helper para respuestas JSON
func jsonResponse(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func jsonError(w http.ResponseWriter, status int, mensaje string) {
jsonResponse(w, status, map[string]string{"error": mensaje})
}