JSON & HTTP
JSON and HTTP are fundamental for modern web applications. Go's standard library provides excellent support for both. In this lesson, you'll learn to work with JSON data and build HTTP clients and servers.
JSON Encoding (Marshal)
Click Run to execute your code
json:"name"- Custom field namejson:"name,omitempty"- Omit if zero valuejson:"-"- Skip this fieldjson:",string"- Encode as string
JSON Decoding (Unmarshal)
Click Run to execute your code
Working with Unknown JSON
// Use map for unknown structure
var data map[string]interface{}
err := json.Unmarshal(jsonData, &data)
// Access fields
name := data["name"].(string)
age := data["age"].(float64) // JSON numbers are float64
// Or use json.RawMessage for partial parsing
type Response struct {
Status string `json:"status"`
Data json.RawMessage `json:"data"` // Parse later
}
HTTP Client
GET Request
resp, err := http.Get("https://api.example.com/users")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
POST Request with JSON
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
user := User{Name: "Alice", Email: "[email protected]"}
jsonData, _ := json.Marshal(user)
resp, err := http.Post(
"https://api.example.com/users",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
Custom HTTP Request
client := &http.Client{
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("GET", "https://api.example.com/data", nil)
if err != nil {
log.Fatal(err)
}
// Add headers
req.Header.Add("Authorization", "Bearer token123")
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// Parse JSON response
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
HTTP Server
Basic Server
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
JSON API Server
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
var users = []User{
{ID: 1, Name: "Alice", Email: "[email protected]"},
{ID: 2, Name: "Bob", Email: "[email protected]"},
}
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func createUser(w http.ResponseWriter, r *http.Request) {
var user User
err := json.NewDecoder(r.Body).Decode(&user)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user.ID = len(users) + 1
users = append(users, user)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(user)
}
func main() {
http.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
getUsers(w, r)
case "POST":
createUser(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
REST API Patterns
Router with Mux
import "github.com/gorilla/mux"
func main() {
r := mux.NewRouter()
// Routes
r.HandleFunc("/users", getUsers).Methods("GET")
r.HandleFunc("/users", createUser).Methods("POST")
r.HandleFunc("/users/{id}", getUser).Methods("GET")
r.HandleFunc("/users/{id}", updateUser).Methods("PUT")
r.HandleFunc("/users/{id}", deleteUser).Methods("DELETE")
log.Fatal(http.ListenAndServe(":8080", r))
}
func getUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id := vars["id"]
// ... find user by id
}
Middleware
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token != "Bearer secret" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// Use middleware
r.Use(loggingMiddleware)
r.Use(authMiddleware)
Common Mistakes
1. Not closing response body
// ❌ Wrong - response body leak
resp, _ := http.Get(url)
body, _ := io.ReadAll(resp.Body)
// ✅ Correct - always close
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
2. Unexported struct fields
// ❌ Wrong - fields won't be marshaled
type User struct {
name string // lowercase = unexported
email string
}
// ✅ Correct - capitalize for export
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
3. Not checking HTTP status
// ❌ Wrong - assuming success
resp, _ := http.Get(url)
json.NewDecoder(resp.Body).Decode(&data)
// ✅ Correct - check status
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
json.NewDecoder(resp.Body).Decode(&data)
Exercise: Weather API Client
Task: Create a weather API client.
Requirements:
- Fetch weather data from a public API
- Parse JSON response into struct
- Display temperature and conditions
- Handle errors properly
Show Solution
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
type WeatherResponse struct {
Main struct {
Temp float64 `json:"temp"`
Humidity int `json:"humidity"`
} `json:"main"`
Weather []struct {
Description string `json:"description"`
} `json:"weather"`
Name string `json:"name"`
}
func getWeather(city, apiKey string) (*WeatherResponse, error) {
url := fmt.Sprintf(
"https://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=metric",
city, apiKey,
)
resp, err := http.Get(url)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("API error: %s - %s", resp.Status, body)
}
var weather WeatherResponse
err = json.NewDecoder(resp.Body).Decode(&weather)
if err != nil {
return nil, fmt.Errorf("decode failed: %w", err)
}
return &weather, nil
}
func main() {
apiKey := "your-api-key" // Get from openweathermap.org
city := "London"
weather, err := getWeather(city, apiKey)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Weather in %s:\n", weather.Name)
fmt.Printf("Temperature: %.1f°C\n", weather.Main.Temp)
fmt.Printf("Humidity: %d%%\n", weather.Main.Humidity)
if len(weather.Weather) > 0 {
fmt.Printf("Conditions: %s\n", weather.Weather[0].Description)
}
}
Summary
- json.Marshal() converts Go → JSON
- json.Unmarshal() converts JSON → Go
- Struct tags control JSON field names
- http.Get/Post() for simple requests
- http.Client for custom requests
- http.HandleFunc() creates server routes
- Always close response bodies
- Check HTTP status codes
What's Next?
Congratulations on completing the Packages & Standard Library module! You now know how to organize code, work with files, and build web services. Next, you'll learn about Testing—how to write tests, benchmarks, and ensure code quality!
Enjoying these tutorials?