Go Web 编程快速入门 08 - JSON API:编码、解码与内容协商

在现代Web开发中,JSON已成为数据交换的标准格式。无论是构建RESTful API还是处理前后端数据交互,掌握JSON的编码解码技术都是必不可少的技能。本文将深入探讨Go语言中JSON处理的各种技巧,从基础操作到高级应用,帮你构建健壮的JSON API系统。

1 JSON编码解码基础

Go语言的encoding/json包为我们提供了强大的JSON处理能力。让我们从最基础的操作开始,逐步深入了解其工作原理。

1.1 基础数据类型的JSON处理

go 复制代码
package main

import (
    "encoding/json"
    "fmt"
    "log"
    "time"
)

// 演示基础类型的JSON编码解码
func basicJSONDemo() {
    // 基础类型编码
    data := map[string]interface{}{
        "name":      "张三",
        "age":       25,
        "is_active": true,
        "score":     98.5,
        "tags":      []string{"Go", "Web", "API"},
        "created_at": time.Now(),
    }
    
    // 编码为JSON
    jsonBytes, err := json.Marshal(data)
    if err != nil {
        log.Fatal("JSON编码失败:", err)
    }
    
    fmt.Printf("编码结果: %s\n\n", jsonBytes)
    
    // 解码JSON
    var decoded map[string]interface{}
    if err := json.Unmarshal(jsonBytes, &decoded); err != nil {
        log.Fatal("JSON解码失败:", err)
    }
    
    fmt.Println("解码结果:")
    for key, value := range decoded {
        fmt.Printf("  %s: %v (类型: %T)\n", key, value, value)
    }
}

这个基础示例展示了Go中JSON处理的核心概念。需要注意的是,解码后的数字类型会变成float64,这是JSON规范的限制。

1.2 结构体与JSON的映射关系

go 复制代码
import (
    "strings"
)

// 用户信息结构体
type User struct {
    ID          int       `json:"id"`
    Name        string    `json:"name"`
    Email       string    `json:"email"`
    Age         int       `json:"age,omitempty"`
    IsActive    bool      `json:"is_active"`
    CreatedAt   time.Time `json:"created_at"`
    UpdatedAt   *time.Time `json:"updated_at,omitempty"`
    Profile     *Profile  `json:"profile,omitempty"`
    Tags        []string  `json:"tags,omitempty"`
    Metadata    map[string]string `json:"metadata,omitempty"`
}

// 用户档案结构体
type Profile struct {
    Avatar      string `json:"avatar,omitempty"`
    Bio         string `json:"bio,omitempty"`
    Website     string `json:"website,omitempty"`
    Location    string `json:"location,omitempty"`
}

// 结构体JSON处理演示
func structJSONDemo() {
    // 创建用户对象
    now := time.Now()
    user := User{
        ID:        1001,
        Name:      "李四",
        Email:     "lisi@example.com",
        Age:       28,
        IsActive:  true,
        CreatedAt: now,
        UpdatedAt: &now,
        Profile: &Profile{
            Avatar:   "https://example.com/avatar.jpg",
            Bio:      "Go语言开发者",
            Website:  "https://lisi.dev",
            Location: "北京",
        },
        Tags:     []string{"Go", "Docker", "Kubernetes"},
        Metadata: map[string]string{
            "department": "技术部",
            "level":      "高级工程师",
        },
    }
    
    // 编码为JSON(格式化输出)
    jsonBytes, err := json.MarshalIndent(user, "", "  ")
    if err != nil {
        log.Fatal("JSON编码失败:", err)
    }
    
    fmt.Printf("用户JSON数据:\n%s\n\n", jsonBytes)
    
    // 从JSON解码
    var decodedUser User
    if err := json.Unmarshal(jsonBytes, &decodedUser); err != nil {
        log.Fatal("JSON解码失败:", err)
    }
    
    fmt.Printf("解码后的用户: %+v\n", decodedUser)
}

1.3 JSON标签的高级用法

go 复制代码
// 商品信息结构体(展示各种JSON标签用法)
type Product struct {
    ID          int     `json:"id"`
    Name        string  `json:"name"`
    Price       float64 `json:"price"`
    
    // omitempty: 空值时不包含在JSON中
    Description string  `json:"description,omitempty"`
    
    // 使用不同的JSON字段名
    SKU         string  `json:"sku_code"`
    
    // 忽略字段(不会出现在JSON中)
    InternalCode string `json:"-"`
    
    // 字符串形式的数字
    Weight      int     `json:"weight,string"`
    
    // 嵌入结构体
    Category    Category `json:"category"`
    
    // 指针类型,支持null值
    Discount    *float64 `json:"discount,omitempty"`
    
    // 自定义时间格式
    LaunchDate  CustomTime `json:"launch_date"`
}

type Category struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

// 自定义时间类型,实现自定义JSON编码解码
type CustomTime struct {
    time.Time
}

// 自定义JSON编码
func (ct CustomTime) MarshalJSON() ([]byte, error) {
    formatted := ct.Time.Format("2006-01-02")
    return json.Marshal(formatted)
}

// 自定义JSON解码
func (ct *CustomTime) UnmarshalJSON(data []byte) error {
    var dateStr string
    if err := json.Unmarshal(data, &dateStr); err != nil {
        return err
    }
    
    parsedTime, err := time.Parse("2006-01-02", dateStr)
    if err != nil {
        return err
    }
    
    ct.Time = parsedTime
    return nil
}

// JSON标签演示
func jsonTagsDemo() {
    discount := 15.5
    product := Product{
        ID:          2001,
        Name:        "Go编程实战",
        Price:       89.99,
        Description: "深入学习Go语言的实战教程",
        SKU:         "BOOK-GO-001",
        InternalCode: "INTERNAL-12345", // 这个字段不会出现在JSON中
        Weight:      500,
        Category: Category{
            ID:   10,
            Name: "编程书籍",
        },
        Discount:   &discount,
        LaunchDate: CustomTime{time.Now()},
    }
    
    // 编码为JSON
    jsonBytes, err := json.MarshalIndent(product, "", "  ")
    if err != nil {
        log.Fatal("JSON编码失败:", err)
    }
    
    fmt.Printf("商品JSON数据:\n%s\n\n", jsonBytes)
    
    // 解码测试
    var decodedProduct Product
    if err := json.Unmarshal(jsonBytes, &decodedProduct); err != nil {
        log.Fatal("JSON解码失败:", err)
    }
    
    fmt.Printf("解码后的商品: %+v\n", decodedProduct)
}

2 高级JSON处理技术

2.1 动态JSON处理

在实际开发中,我们经常需要处理结构不固定的JSON数据。Go提供了多种方式来处理这种情况。

go 复制代码
import (
    "reflect"
    "strconv"
)

// JSON处理器
type JSONProcessor struct {
    data map[string]interface{}
}

func NewJSONProcessor(jsonData []byte) (*JSONProcessor, error) {
    var data map[string]interface{}
    if err := json.Unmarshal(jsonData, &data); err != nil {
        return nil, fmt.Errorf("JSON解析失败: %w", err)
    }
    
    return &JSONProcessor{data: data}, nil
}

// 获取字符串值(带默认值)
func (jp *JSONProcessor) GetString(key, defaultValue string) string {
    if value, exists := jp.data[key]; exists {
        if str, ok := value.(string); ok {
            return str
        }
    }
    return defaultValue
}

// 获取整数值(带类型转换)
func (jp *JSONProcessor) GetInt(key string, defaultValue int) int {
    if value, exists := jp.data[key]; exists {
        switch v := value.(type) {
        case int:
            return v
        case float64:
            return int(v)
        case string:
            if num, err := strconv.Atoi(v); err == nil {
                return num
            }
        }
    }
    return defaultValue
}

// 获取浮点数值
func (jp *JSONProcessor) GetFloat(key string, defaultValue float64) float64 {
    if value, exists := jp.data[key]; exists {
        switch v := value.(type) {
        case float64:
            return v
        case int:
            return float64(v)
        case string:
            if num, err := strconv.ParseFloat(v, 64); err == nil {
                return num
            }
        }
    }
    return defaultValue
}

// 获取布尔值
func (jp *JSONProcessor) GetBool(key string, defaultValue bool) bool {
    if value, exists := jp.data[key]; exists {
        switch v := value.(type) {
        case bool:
            return v
        case string:
            return strings.ToLower(v) == "true"
        case float64:
            return v != 0
        }
    }
    return defaultValue
}

// 获取数组值
func (jp *JSONProcessor) GetArray(key string) []interface{} {
    if value, exists := jp.data[key]; exists {
        if arr, ok := value.([]interface{}); ok {
            return arr
        }
    }
    return nil
}

// 获取嵌套对象
func (jp *JSONProcessor) GetObject(key string) *JSONProcessor {
    if value, exists := jp.data[key]; exists {
        if obj, ok := value.(map[string]interface{}); ok {
            return &JSONProcessor{data: obj}
        }
    }
    return nil
}

// 设置值
func (jp *JSONProcessor) Set(key string, value interface{}) {
    jp.data[key] = value
}

// 删除键
func (jp *JSONProcessor) Delete(key string) {
    delete(jp.data, key)
}

// 检查键是否存在
func (jp *JSONProcessor) Has(key string) bool {
    _, exists := jp.data[key]
    return exists
}

// 获取所有键
func (jp *JSONProcessor) Keys() []string {
    keys := make([]string, 0, len(jp.data))
    for key := range jp.data {
        keys = append(keys, key)
    }
    return keys
}

// 转换为JSON字节
func (jp *JSONProcessor) ToJSON() ([]byte, error) {
    return json.Marshal(jp.data)
}

// 转换为格式化JSON
func (jp *JSONProcessor) ToJSONIndent() ([]byte, error) {
    return json.MarshalIndent(jp.data, "", "  ")
}

// 动态JSON处理演示
func dynamicJSONDemo() {
    // 模拟接收到的JSON数据
    jsonData := `{
        "user_id": "12345",
        "name": "王五",
        "age": "30",
        "is_premium": "true",
        "scores": [85, 92, 78],
        "profile": {
            "avatar": "avatar.jpg",
            "bio": "软件工程师"
        },
        "metadata": {
            "last_login": "2024-01-15T10:30:00Z",
            "login_count": 156
        }
    }`
    
    // 创建JSON处理器
    processor, err := NewJSONProcessor([]byte(jsonData))
    if err != nil {
        log.Fatal("创建JSON处理器失败:", err)
    }
    
    // 提取各种类型的数据
    userID := processor.GetString("user_id", "")
    name := processor.GetString("name", "未知用户")
    age := processor.GetInt("age", 0)
    isPremium := processor.GetBool("is_premium", false)
    scores := processor.GetArray("scores")
    
    fmt.Printf("用户ID: %s\n", userID)
    fmt.Printf("姓名: %s\n", name)
    fmt.Printf("年龄: %d\n", age)
    fmt.Printf("高级用户: %t\n", isPremium)
    fmt.Printf("分数: %v\n", scores)
    
    // 处理嵌套对象
    if profile := processor.GetObject("profile"); profile != nil {
        avatar := profile.GetString("avatar", "")
        bio := profile.GetString("bio", "")
        fmt.Printf("头像: %s\n", avatar)
        fmt.Printf("简介: %s\n", bio)
    }
    
    // 修改数据
    processor.Set("last_updated", time.Now().Format(time.RFC3339))
    processor.Set("processed", true)
    
    // 输出修改后的JSON
    if modifiedJSON, err := processor.ToJSONIndent(); err == nil {
        fmt.Printf("\n修改后的JSON:\n%s\n", modifiedJSON)
    }
}

2.2 JSON流式处理

对于大型JSON数据,流式处理可以显著减少内存使用。

go 复制代码
import (
    "io"
    "strings"
)

// JSON流处理器
type JSONStreamProcessor struct {
    decoder *json.Decoder
    encoder *json.Encoder
}

func NewJSONStreamProcessor(reader io.Reader, writer io.Writer) *JSONStreamProcessor {
    return &JSONStreamProcessor{
        decoder: json.NewDecoder(reader),
        encoder: json.NewEncoder(writer),
    }
}

// 处理JSON数组流
func (jsp *JSONStreamProcessor) ProcessArray(handler func(interface{}) error) error {
    // 读取数组开始标记
    token, err := jsp.decoder.Token()
    if err != nil {
        return err
    }
    
    if delim, ok := token.(json.Delim); !ok || delim != '[' {
        return fmt.Errorf("期望数组开始标记,得到: %v", token)
    }
    
    // 处理数组中的每个元素
    for jsp.decoder.More() {
        var element interface{}
        if err := jsp.decoder.Decode(&element); err != nil {
            return err
        }
        
        if err := handler(element); err != nil {
            return err
        }
    }
    
    // 读取数组结束标记
    token, err = jsp.decoder.Token()
    if err != nil {
        return err
    }
    
    if delim, ok := token.(json.Delim); !ok || delim != ']' {
        return fmt.Errorf("期望数组结束标记,得到: %v", token)
    }
    
    return nil
}

// 处理JSON对象流
func (jsp *JSONStreamProcessor) ProcessObject(handler func(string, interface{}) error) error {
    // 读取对象开始标记
    token, err := jsp.decoder.Token()
    if err != nil {
        return err
    }
    
    if delim, ok := token.(json.Delim); !ok || delim != '{' {
        return fmt.Errorf("期望对象开始标记,得到: %v", token)
    }
    
    // 处理对象中的每个键值对
    for jsp.decoder.More() {
        // 读取键
        keyToken, err := jsp.decoder.Token()
        if err != nil {
            return err
        }
        
        key, ok := keyToken.(string)
        if !ok {
            return fmt.Errorf("期望字符串键,得到: %v", keyToken)
        }
        
        // 读取值
        var value interface{}
        if err := jsp.decoder.Decode(&value); err != nil {
            return err
        }
        
        if err := handler(key, value); err != nil {
            return err
        }
    }
    
    // 读取对象结束标记
    token, err = jsp.decoder.Token()
    if err != nil {
        return err
    }
    
    if delim, ok := token.(json.Delim); !ok || delim != '}' {
        return fmt.Errorf("期望对象结束标记,得到: %v", token)
    }
    
    return nil
}

// 编码并写入数据
func (jsp *JSONStreamProcessor) Encode(data interface{}) error {
    return jsp.encoder.Encode(data)
}

// 流式处理演示
func streamProcessingDemo() {
    // 模拟大型JSON数组
    jsonArray := `[
        {"id": 1, "name": "用户1", "score": 85},
        {"id": 2, "name": "用户2", "score": 92},
        {"id": 3, "name": "用户3", "score": 78},
        {"id": 4, "name": "用户4", "score": 96},
        {"id": 5, "name": "用户5", "score": 88}
    ]`
    
    reader := strings.NewReader(jsonArray)
    var output strings.Builder
    
    processor := NewJSONStreamProcessor(reader, &output)
    
    fmt.Println("流式处理JSON数组:")
    
    // 处理数组中的每个用户
    err := processor.ProcessArray(func(element interface{}) error {
        if user, ok := element.(map[string]interface{}); ok {
            id := user["id"]
            name := user["name"]
            score := user["score"]
            
            fmt.Printf("处理用户: ID=%v, 姓名=%v, 分数=%v\n", id, name, score)
            
            // 可以在这里进行数据转换、验证等操作
            if scoreFloat, ok := score.(float64); ok && scoreFloat >= 90 {
                fmt.Printf("  -> 高分用户!\n")
            }
        }
        return nil
    })
    
    if err != nil {
        log.Fatal("流式处理失败:", err)
    }
}

3 RESTful API设计与实现

3.1 标准化的API响应结构

go 复制代码
import (
    "net/http"
)

// 标准API响应结构
type APIResponse struct {
    Success   bool        `json:"success"`
    Message   string      `json:"message,omitempty"`
    Data      interface{} `json:"data,omitempty"`
    Error     *APIError   `json:"error,omitempty"`
    Meta      *Meta       `json:"meta,omitempty"`
    Timestamp int64       `json:"timestamp"`
}

// API错误结构
type APIError struct {
    Code    string `json:"code"`
    Message string `json:"message"`
    Details string `json:"details,omitempty"`
}

// 元数据结构(用于分页等)
type Meta struct {
    Page       int `json:"page,omitempty"`
    PageSize   int `json:"page_size,omitempty"`
    Total      int `json:"total,omitempty"`
    TotalPages int `json:"total_pages,omitempty"`
}

// API响应构建器
type ResponseBuilder struct {
    response *APIResponse
}

func NewResponseBuilder() *ResponseBuilder {
    return &ResponseBuilder{
        response: &APIResponse{
            Timestamp: time.Now().Unix(),
        },
    }
}

// 成功响应
func (rb *ResponseBuilder) Success(data interface{}) *ResponseBuilder {
    rb.response.Success = true
    rb.response.Data = data
    return rb
}

// 错误响应
func (rb *ResponseBuilder) Error(code, message, details string) *ResponseBuilder {
    rb.response.Success = false
    rb.response.Error = &APIError{
        Code:    code,
        Message: message,
        Details: details,
    }
    return rb
}

// 设置消息
func (rb *ResponseBuilder) Message(message string) *ResponseBuilder {
    rb.response.Message = message
    return rb
}

// 设置元数据
func (rb *ResponseBuilder) Meta(meta *Meta) *ResponseBuilder {
    rb.response.Meta = meta
    return rb
}

// 构建响应
func (rb *ResponseBuilder) Build() *APIResponse {
    return rb.response
}

// JSON响应助手
func WriteJSONResponse(w http.ResponseWriter, statusCode int, response *APIResponse) {
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    w.WriteHeader(statusCode)
    
    if err := json.NewEncoder(w).Encode(response); err != nil {
        log.Printf("JSON编码失败: %v", err)
        http.Error(w, "内部服务器错误", http.StatusInternalServerError)
    }
}

// 便捷的响应函数
func WriteSuccess(w http.ResponseWriter, data interface{}) {
    response := NewResponseBuilder().Success(data).Build()
    WriteJSONResponse(w, http.StatusOK, response)
}

func WriteError(w http.ResponseWriter, statusCode int, code, message, details string) {
    response := NewResponseBuilder().Error(code, message, details).Build()
    WriteJSONResponse(w, statusCode, response)
}

func WriteCreated(w http.ResponseWriter, data interface{}) {
    response := NewResponseBuilder().Success(data).Message("创建成功").Build()
    WriteJSONResponse(w, http.StatusCreated, response)
}

func WriteUpdated(w http.ResponseWriter, data interface{}) {
    response := NewResponseBuilder().Success(data).Message("更新成功").Build()
    WriteJSONResponse(w, http.StatusOK, response)
}

func WriteDeleted(w http.ResponseWriter) {
    response := NewResponseBuilder().Success(nil).Message("删除成功").Build()
    WriteJSONResponse(w, http.StatusOK, response)
}

3.2 用户管理API实现

go 复制代码
// 用户管理API
type UserAPI struct {
    users map[int]*User // 简化存储,实际项目中应使用数据库
    nextID int
}

func NewUserAPI() *UserAPI {
    return &UserAPI{
        users:  make(map[int]*User),
        nextID: 1,
    }
}

// 创建用户
func (api *UserAPI) CreateUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
        return
    }
    
    // 解析请求体
    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())
        return
    }
    
    // 验证必需字段
    if user.Name == "" {
        WriteError(w, http.StatusBadRequest, "MISSING_NAME", "用户名不能为空", "")
        return
    }
    
    if user.Email == "" {
        WriteError(w, http.StatusBadRequest, "MISSING_EMAIL", "邮箱不能为空", "")
        return
    }
    
    // 检查邮箱是否已存在
    for _, existingUser := range api.users {
        if existingUser.Email == user.Email {
            WriteError(w, http.StatusConflict, "EMAIL_EXISTS", "邮箱已存在", "")
            return
        }
    }
    
    // 设置用户ID和创建时间
    user.ID = api.nextID
    api.nextID++
    user.CreatedAt = time.Now()
    user.IsActive = true
    
    // 存储用户
    api.users[user.ID] = &user
    
    WriteCreated(w, user)
}

// 获取用户列表
func (api *UserAPI) GetUsers(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
        return
    }
    
    // 解析查询参数
    query := r.URL.Query()
    page := 1
    pageSize := 10
    
    if pageStr := query.Get("page"); pageStr != "" {
        if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
            page = p
        }
    }
    
    if sizeStr := query.Get("page_size"); sizeStr != "" {
        if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 && s <= 100 {
            pageSize = s
        }
    }
    
    // 获取所有用户
    allUsers := make([]*User, 0, len(api.users))
    for _, user := range api.users {
        allUsers = append(allUsers, user)
    }
    
    // 计算分页
    total := len(allUsers)
    start := (page - 1) * pageSize
    end := start + pageSize
    
    if start >= total {
        allUsers = []*User{}
    } else {
        if end > total {
            end = total
        }
        allUsers = allUsers[start:end]
    }
    
    // 构建响应
    meta := &Meta{
        Page:       page,
        PageSize:   pageSize,
        Total:      total,
        TotalPages: (total + pageSize - 1) / pageSize,
    }
    
    response := NewResponseBuilder().Success(allUsers).Meta(meta).Build()
    WriteJSONResponse(w, http.StatusOK, response)
}

// 获取单个用户
func (api *UserAPI) GetUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
        return
    }
    
    // 从URL路径提取用户ID(简化实现)
    path := r.URL.Path
    parts := strings.Split(path, "/")
    if len(parts) < 3 {
        WriteError(w, http.StatusBadRequest, "INVALID_PATH", "无效的路径", "")
        return
    }
    
    userID, err := strconv.Atoi(parts[len(parts)-1])
    if err != nil {
        WriteError(w, http.StatusBadRequest, "INVALID_USER_ID", "无效的用户ID", "")
        return
    }
    
    // 查找用户
    user, exists := api.users[userID]
    if !exists {
        WriteError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在", "")
        return
    }
    
    WriteSuccess(w, user)
}

// 更新用户
func (api *UserAPI) UpdateUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPut {
        WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
        return
    }
    
    // 提取用户ID
    path := r.URL.Path
    parts := strings.Split(path, "/")
    if len(parts) < 3 {
        WriteError(w, http.StatusBadRequest, "INVALID_PATH", "无效的路径", "")
        return
    }
    
    userID, err := strconv.Atoi(parts[len(parts)-1])
    if err != nil {
        WriteError(w, http.StatusBadRequest, "INVALID_USER_ID", "无效的用户ID", "")
        return
    }
    
    // 检查用户是否存在
    existingUser, exists := api.users[userID]
    if !exists {
        WriteError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在", "")
        return
    }
    
    // 解析更新数据
    var updateData User
    if err := json.NewDecoder(r.Body).Decode(&updateData); err != nil {
        WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())
        return
    }
    
    // 更新用户信息(保留原有的ID和创建时间)
    updateData.ID = existingUser.ID
    updateData.CreatedAt = existingUser.CreatedAt
    now := time.Now()
    updateData.UpdatedAt = &now
    
    // 验证邮箱唯一性
    if updateData.Email != existingUser.Email {
        for id, user := range api.users {
            if id != userID && user.Email == updateData.Email {
                WriteError(w, http.StatusConflict, "EMAIL_EXISTS", "邮箱已存在", "")
                return
            }
        }
    }
    
    // 保存更新
    api.users[userID] = &updateData
    
    WriteUpdated(w, updateData)
}

// 删除用户
func (api *UserAPI) DeleteUser(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodDelete {
        WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
        return
    }
    
    // 提取用户ID
    path := r.URL.Path
    parts := strings.Split(path, "/")
    if len(parts) < 3 {
        WriteError(w, http.StatusBadRequest, "INVALID_PATH", "无效的路径", "")
        return
    }
    
    userID, err := strconv.Atoi(parts[len(parts)-1])
    if err != nil {
        WriteError(w, http.StatusBadRequest, "INVALID_USER_ID", "无效的用户ID", "")
        return
    }
    
    // 检查用户是否存在
    if _, exists := api.users[userID]; !exists {
        WriteError(w, http.StatusNotFound, "USER_NOT_FOUND", "用户不存在", "")
        return
    }
    
    // 删除用户
    delete(api.users, userID)
    
    WriteDeleted(w)
}

4 内容协商与格式处理

4.1 内容协商中间件

go 复制代码
import (
    "compress/gzip"
    "strings"
)

// 支持的内容类型
const (
    ContentTypeJSON = "application/json"
    ContentTypeXML  = "application/xml"
    ContentTypeText = "text/plain"
    ContentTypeHTML = "text/html"
)

// 内容协商中间件
func ContentNegotiationMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // 解析Accept头
        acceptHeader := r.Header.Get("Accept")
        contentType := negotiateContentType(acceptHeader)
        
        // 设置响应内容类型
        w.Header().Set("Content-Type", contentType)
        
        // 将协商结果存储到上下文中
        ctx := context.WithValue(r.Context(), "content_type", contentType)
        
        next(w, r.WithContext(ctx))
    }
}

// 协商内容类型
func negotiateContentType(acceptHeader string) string {
    if acceptHeader == "" {
        return ContentTypeJSON // 默认返回JSON
    }
    
    // 解析Accept头中的媒体类型
    mediaTypes := parseAcceptHeader(acceptHeader)
    
    // 按优先级排序并选择支持的类型
    for _, mediaType := range mediaTypes {
        switch {
        case strings.Contains(mediaType.Type, "application/json"):
            return ContentTypeJSON
        case strings.Contains(mediaType.Type, "application/xml"):
            return ContentTypeXML
        case strings.Contains(mediaType.Type, "text/plain"):
            return ContentTypeText
        case strings.Contains(mediaType.Type, "text/html"):
            return ContentTypeHTML
        }
    }
    
    return ContentTypeJSON // 默认返回JSON
}

// 媒体类型结构
type MediaType struct {
    Type     string
    Quality  float64
    Params   map[string]string
}

// 解析Accept头
func parseAcceptHeader(acceptHeader string) []MediaType {
    var mediaTypes []MediaType
    
    parts := strings.Split(acceptHeader, ",")
    for _, part := range parts {
        part = strings.TrimSpace(part)
        if part == "" {
            continue
        }
        
        mediaType := parseMediaType(part)
        mediaTypes = append(mediaTypes, mediaType)
    }
    
    // 按质量值排序(简化实现)
    // 实际项目中应该实现完整的排序逻辑
    
    return mediaTypes
}

// 解析单个媒体类型
func parseMediaType(mediaTypeStr string) MediaType {
    mediaType := MediaType{
        Quality: 1.0,
        Params:  make(map[string]string),
    }
    
    parts := strings.Split(mediaTypeStr, ";")
    mediaType.Type = strings.TrimSpace(parts[0])
    
    // 解析参数
    for i := 1; i < len(parts); i++ {
        param := strings.TrimSpace(parts[i])
        if strings.HasPrefix(param, "q=") {
            if quality, err := strconv.ParseFloat(param[2:], 64); err == nil {
                mediaType.Quality = quality
            }
        } else {
            keyValue := strings.SplitN(param, "=", 2)
            if len(keyValue) == 2 {
                mediaType.Params[keyValue[0]] = keyValue[1]
            }
        }
    }
    
    return mediaType
}

// 压缩中间件
func CompressionMiddleware(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        // 检查客户端是否支持gzip压缩
        if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
            next(w, r)
            return
        }
        
        // 设置压缩响应头
        w.Header().Set("Content-Encoding", "gzip")
        w.Header().Set("Vary", "Accept-Encoding")
        
        // 创建gzip写入器
        gzipWriter := gzip.NewWriter(w)
        defer gzipWriter.Close()
        
        // 包装响应写入器
        gzipResponseWriter := &GzipResponseWriter{
            ResponseWriter: w,
            Writer:         gzipWriter,
        }
        
        next(gzipResponseWriter, r)
    }
}

// Gzip响应写入器
type GzipResponseWriter struct {
    http.ResponseWriter
    Writer io.Writer
}

func (grw *GzipResponseWriter) Write(data []byte) (int, error) {
    return grw.Writer.Write(data)
}

// 多格式响应处理器
func MultiFormatResponse(w http.ResponseWriter, r *http.Request, data interface{}) {
    contentType := r.Context().Value("content_type").(string)
    
    switch contentType {
    case ContentTypeJSON:
        writeJSONResponse(w, data)
    case ContentTypeXML:
        writeXMLResponse(w, data)
    case ContentTypeText:
        writeTextResponse(w, data)
    case ContentTypeHTML:
        writeHTMLResponse(w, data)
    default:
        writeJSONResponse(w, data)
    }
}

func writeJSONResponse(w http.ResponseWriter, data interface{}) {
    w.Header().Set("Content-Type", "application/json; charset=utf-8")
    json.NewEncoder(w).Encode(data)
}

func writeXMLResponse(w http.ResponseWriter, data interface{}) {
    w.Header().Set("Content-Type", "application/xml; charset=utf-8")
    // 这里需要实现XML编码逻辑
    fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<data>%v</data>", data)
}

func writeTextResponse(w http.ResponseWriter, data interface{}) {
    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    fmt.Fprintf(w, "%v", data)
}

func writeHTMLResponse(w http.ResponseWriter, data interface{}) {
    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    fmt.Fprintf(w, "<html><body><pre>%v</pre></body></html>", data)
}

5 完整示例:图书管理API

让我们通过一个完整的图书管理API来综合运用本章学到的所有技术。

go 复制代码
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"
)

// 图书结构体
type Book struct {
    ID          int       `json:"id"`
    Title       string    `json:"title"`
    Author      string    `json:"author"`
    ISBN        string    `json:"isbn"`
    Price       float64   `json:"price"`
    Category    string    `json:"category"`
    Description string    `json:"description,omitempty"`
    PublishedAt CustomTime `json:"published_at"`
    CreatedAt   time.Time `json:"created_at"`
    UpdatedAt   *time.Time `json:"updated_at,omitempty"`
}

// 图书管理API
type BookAPI struct {
    books  map[int]*Book
    nextID int
}

func NewBookAPI() *BookAPI {
    api := &BookAPI{
        books:  make(map[int]*Book),
        nextID: 1,
    }
    
    // 添加一些示例数据
    api.seedData()
    
    return api
}

// 添加示例数据
func (api *BookAPI) seedData() {
    books := []*Book{
        {
            Title:       "Go语言编程",
            Author:      "许式伟",
            ISBN:        "978-7-115-29036-9",
            Price:       59.00,
            Category:    "编程",
            Description: "Go语言编程入门与实战",
            PublishedAt: CustomTime{time.Date(2012, 8, 1, 0, 0, 0, 0, time.UTC)},
        },
        {
            Title:       "深入理解计算机系统",
            Author:      "Randal E. Bryant",
            ISBN:        "978-7-111-54493-7",
            Price:       139.00,
            Category:    "计算机科学",
            Description: "计算机系统的经典教材",
            PublishedAt: CustomTime{time.Date(2016, 11, 1, 0, 0, 0, 0, time.UTC)},
        },
    }
    
    for _, book := range books {
        book.ID = api.nextID
        api.nextID++
        book.CreatedAt = time.Now()
        api.books[book.ID] = book
    }
}

// 设置路由
func (api *BookAPI) SetupRoutes() {
    // 应用中间件
    http.HandleFunc("/api/books", 
        CompressionMiddleware(
            ContentNegotiationMiddleware(api.handleBooks)))
    
    http.HandleFunc("/api/books/", 
        CompressionMiddleware(
            ContentNegotiationMiddleware(api.handleBook)))
    
    // 健康检查端点
    http.HandleFunc("/health", api.healthCheck)
    
    // API文档端点
    http.HandleFunc("/api/docs", api.apiDocs)
}

// 处理图书集合请求
func (api *BookAPI) handleBooks(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        api.getBooks(w, r)
    case http.MethodPost:
        api.createBook(w, r)
    default:
        WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
    }
}

// 处理单个图书请求
func (api *BookAPI) handleBook(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case http.MethodGet:
        api.getBook(w, r)
    case http.MethodPut:
        api.updateBook(w, r)
    case http.MethodDelete:
        api.deleteBook(w, r)
    default:
        WriteError(w, http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED", "方法不允许", "")
    }
}

// 获取图书列表
func (api *BookAPI) getBooks(w http.ResponseWriter, r *http.Request) {
    query := r.URL.Query()
    
    // 解析查询参数
    category := query.Get("category")
    author := query.Get("author")
    
    page := 1
    pageSize := 10
    
    if pageStr := query.Get("page"); pageStr != "" {
        if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
            page = p
        }
    }
    
    if sizeStr := query.Get("page_size"); sizeStr != "" {
        if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 && s <= 100 {
            pageSize = s
        }
    }
    
    // 过滤图书
    var filteredBooks []*Book
    for _, book := range api.books {
        if category != "" && book.Category != category {
            continue
        }
        if author != "" && !strings.Contains(strings.ToLower(book.Author), strings.ToLower(author)) {
            continue
        }
        filteredBooks = append(filteredBooks, book)
    }
    
    // 分页处理
    total := len(filteredBooks)
    start := (page - 1) * pageSize
    end := start + pageSize
    
    if start >= total {
        filteredBooks = []*Book{}
    } else {
        if end > total {
            end = total
        }
        filteredBooks = filteredBooks[start:end]
    }
    
    // 构建响应
    meta := &Meta{
        Page:       page,
        PageSize:   pageSize,
        Total:      total,
        TotalPages: (total + pageSize - 1) / pageSize,
    }
    
    response := NewResponseBuilder().Success(filteredBooks).Meta(meta).Build()
    WriteJSONResponse(w, http.StatusOK, response)
}

// 创建图书
func (api *BookAPI) createBook(w http.ResponseWriter, r *http.Request) {
    var book Book
    if err := json.NewDecoder(r.Body).Decode(&book); err != nil {
        WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())
        return
    }
    
    // 验证必需字段
    if book.Title == "" {
        WriteError(w, http.StatusBadRequest, "MISSING_TITLE", "书名不能为空", "")
        return
    }
    
    if book.Author == "" {
        WriteError(w, http.StatusBadRequest, "MISSING_AUTHOR", "作者不能为空", "")
        return
    }
    
    if book.ISBN == "" {
        WriteError(w, http.StatusBadRequest, "MISSING_ISBN", "ISBN不能为空", "")
        return
    }
    
    // 检查ISBN是否已存在
    for _, existingBook := range api.books {
        if existingBook.ISBN == book.ISBN {
            WriteError(w, http.StatusConflict, "ISBN_EXISTS", "ISBN已存在", "")
            return
        }
    }
    
    // 设置图书信息
    book.ID = api.nextID
    api.nextID++
    book.CreatedAt = time.Now()
    
    // 存储图书
    api.books[book.ID] = &book
    
    WriteCreated(w, book)
}

// 获取单个图书
func (api *BookAPI) getBook(w http.ResponseWriter, r *http.Request) {
    bookID := api.extractBookID(r.URL.Path)
    if bookID == 0 {
        WriteError(w, http.StatusBadRequest, "INVALID_BOOK_ID", "无效的图书ID", "")
        return
    }
    
    book, exists := api.books[bookID]
    if !exists {
        WriteError(w, http.StatusNotFound, "BOOK_NOT_FOUND", "图书不存在", "")
        return
    }
    
    WriteSuccess(w, book)
}

// 更新图书
func (api *BookAPI) updateBook(w http.ResponseWriter, r *http.Request) {
    bookID := api.extractBookID(r.URL.Path)
    if bookID == 0 {
        WriteError(w, http.StatusBadRequest, "INVALID_BOOK_ID", "无效的图书ID", "")
        return
    }
    
    existingBook, exists := api.books[bookID]
    if !exists {
        WriteError(w, http.StatusNotFound, "BOOK_NOT_FOUND", "图书不存在", "")
        return
    }
    
    var updateData Book
    if err := json.NewDecoder(r.Body).Decode(&updateData); err != nil {
        WriteError(w, http.StatusBadRequest, "INVALID_JSON", "无效的JSON格式", err.Error())
        return
    }
    
    // 保留原有信息
    updateData.ID = existingBook.ID
    updateData.CreatedAt = existingBook.CreatedAt
    now := time.Now()
    updateData.UpdatedAt = &now
    
    // 检查ISBN唯一性
    if updateData.ISBN != existingBook.ISBN {
        for id, book := range api.books {
            if id != bookID && book.ISBN == updateData.ISBN {
                WriteError(w, http.StatusConflict, "ISBN_EXISTS", "ISBN已存在", "")
                return
            }
        }
    }
    
    // 保存更新
    api.books[bookID] = &updateData
    
    WriteUpdated(w, updateData)
}

// 删除图书
func (api *BookAPI) deleteBook(w http.ResponseWriter, r *http.Request) {
    bookID := api.extractBookID(r.URL.Path)
    if bookID == 0 {
        WriteError(w, http.StatusBadRequest, "INVALID_BOOK_ID", "无效的图书ID", "")
        return
    }
    
    if _, exists := api.books[bookID]; !exists {
        WriteError(w, http.StatusNotFound, "BOOK_NOT_FOUND", "图书不存在", "")
        return
    }
    
    delete(api.books, bookID)
    WriteDeleted(w)
}

// 从URL路径提取图书ID
func (api *BookAPI) extractBookID(path string) int {
    parts := strings.Split(path, "/")
    if len(parts) < 4 {
        return 0
    }
    
    idStr := parts[len(parts)-1]
    if id, err := strconv.Atoi(idStr); err == nil {
        return id
    }
    
    return 0
}

// 健康检查
func (api *BookAPI) healthCheck(w http.ResponseWriter, r *http.Request) {
    health := map[string]interface{}{
        "status":    "healthy",
        "timestamp": time.Now().Unix(),
        "version":   "1.0.0",
        "books_count": len(api.books),
    }
    
    WriteSuccess(w, health)
}

// API文档
func (api *BookAPI) apiDocs(w http.ResponseWriter, r *http.Request) {
    docs := map[string]interface{}{
        "title":       "图书管理API",
        "version":     "1.0.0",
        "description": "简单的图书管理RESTful API",
        "endpoints": map[string]interface{}{
            "GET /api/books":      "获取图书列表",
            "POST /api/books":     "创建新图书",
            "GET /api/books/{id}": "获取指定图书",
            "PUT /api/books/{id}": "更新指定图书",
            "DELETE /api/books/{id}": "删除指定图书",
            "GET /health":         "健康检查",
        },
        "query_parameters": map[string]string{
            "page":      "页码(默认1)",
            "page_size": "每页大小(默认10,最大100)",
            "category":  "图书分类过滤",
            "author":    "作者名称过滤",
        },
    }
    
    WriteSuccess(w, docs)
}

// 主函数
func main() {
    // 创建API实例
    bookAPI := NewBookAPI()
    bookAPI.SetupRoutes()
    
    // 创建HTTP服务器
    server := &http.Server{
        Addr:         ":8080",
        ReadTimeout:  15 * time.Second,
        WriteTimeout: 15 * time.Second,
        IdleTimeout:  60 * time.Second,
    }
    
    // 启动服务器
    go func() {
        fmt.Println("图书管理API服务启动在 http://localhost:8080")
        fmt.Println("API端点:")
        fmt.Println("  GET    /api/books          - 获取图书列表")
        fmt.Println("  POST   /api/books          - 创建新图书")
        fmt.Println("  GET    /api/books/{id}     - 获取指定图书")
        fmt.Println("  PUT    /api/books/{id}     - 更新指定图书")
        fmt.Println("  DELETE /api/books/{id}     - 删除指定图书")
        fmt.Println("  GET    /health             - 健康检查")
        fmt.Println("  GET    /api/docs           - API文档")
        
        if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatal("服务器启动失败:", err)
        }
    }()
    
    // 优雅关闭
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit
    
    fmt.Println("正在关闭服务器...")
    
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    if err := server.Shutdown(ctx); err != nil {
        log.Fatal("服务器关闭失败:", err)
    }
    
    fmt.Println("服务器已关闭")
}

6 测试与调试

6.1 API测试示例

go 复制代码
// 测试用例
func testBookAPI() {
    // 测试创建图书
    fmt.Println("=== 测试创建图书 ===")
    createBookJSON := `{
        "title": "Go Web编程实战",
        "author": "张三",
        "isbn": "978-7-111-12345-6",
        "price": 79.99,
        "category": "编程",
        "description": "Go语言Web开发完整指南",
        "published_at": "2024-01-15"
    }`
    
    resp, err := http.Post("http://localhost:8080/api/books", 
        "application/json", 
        strings.NewReader(createBookJSON))
    if err != nil {
        log.Fatal("请求失败:", err)
    }
    defer resp.Body.Close()
    
    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("状态码: %d\n", resp.StatusCode)
    fmt.Printf("响应: %s\n\n", body)
    
    // 测试获取图书列表
    fmt.Println("=== 测试获取图书列表 ===")
    resp, err = http.Get("http://localhost:8080/api/books?page=1&page_size=5")
    if err != nil {
        log.Fatal("请求失败:", err)
    }
    defer resp.Body.Close()
    
    body, _ = io.ReadAll(resp.Body)
    fmt.Printf("状态码: %d\n", resp.StatusCode)
    fmt.Printf("响应: %s\n\n", body)
    
    // 测试获取单个图书
    fmt.Println("=== 测试获取单个图书 ===")
    resp, err = http.Get("http://localhost:8080/api/books/1")
    if err != nil {
        log.Fatal("请求失败:", err)
    }
    defer resp.Body.Close()
    
    body, _ = io.ReadAll(resp.Body)
    fmt.Printf("状态码: %d\n", resp.StatusCode)
    fmt.Printf("响应: %s\n\n", body)
}

通过这篇文章,我们深入学习了Go语言中JSON API的设计与实现。从基础的JSON编码解码到高级的内容协商,从简单的数据处理到完整的RESTful API系统,这些技能为构建现代Web应用奠定了坚实基础。

在实际项目中,建议根据业务需求选择合适的JSON处理方案,并建立统一的API设计规范。合理的API设计不仅能提升开发效率,还能为系统的扩展和维护提供便利。

相关推荐
红宝村村长3 小时前
Golang交叉编译到Android上运行
android·开发语言·golang
虚行3 小时前
Go 编程基础
开发语言·后端·golang
脚踏实地的大梦想家3 小时前
【Go】P14 Go语言核心利器:全面解析结构体 (Struct)
开发语言·后端·golang
虚行3 小时前
Go学习资料整理
golang·区块链
QX_hao3 小时前
【Go】--time包的使用
开发语言·后端·golang
IT_陈寒3 小时前
Vite 3.0终极提速指南:5个鲜为人知的配置技巧让构建效率翻倍
前端·人工智能·后端
武子康3 小时前
大数据-137 ClickHouse MergeTree 实战指南|分区、稀疏索引与合并机制 存储结构 一级索引 跳数索引
大数据·后端·nosql
霍格沃兹_测试4 小时前
软件测试 | 测试开发 | app自动化测试之Appium 原理 与 JsonWP 协议分析
json
程序员鱼皮4 小时前
前后端分离,千万别再搞错了!
java·前端·后端·计算机·程序员·编程·软件开发