JSON 绑定

JSON 是 API 最常用的数据格式。Gin 提供了便捷的 JSON 绑定功能。

基本用法

type User struct {
    Name  string `json:"name"`
    Email string `json:"email"`
}

r.POST("/users", func(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, user)
})

请求:

curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"alice@example.com"}'

JSON 标签

结构体标签控制 JSON 字段映射:

type User struct {
    Name     string `json:"name"`
    Email    string `json:"email"`
    Password string `json:"-"`        // 忽略此字段
    Age      int    `json:"age,omitempty"` // 为空时忽略
    CreatedAt string `json:"created_at"` // 重命名字段
}

标签选项

选项说明
json:"name"字段名映射
json:"-"忽略字段
json:",omitempty"零值时不输出
json:"name,omitempty"组合使用

嵌套结构体

type Address struct {
    City    string `json:"city"`
    Country string `json:"country"`
}

type User struct {
    Name    string  `json:"name"`
    Address Address `json:"address"`
}

r.POST("/users", func(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, user)
})

请求:

{
    "name": "Alice",
    "address": {
        "city": "Beijing",
        "country": "China"
    }
}

数组和切片

type CreateOrderRequest struct {
    UserID  string   `json:"user_id"`
    Items   []string `json:"items"`
}

r.POST("/orders", func(c *gin.Context) {
    var req CreateOrderRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, gin.H{
        "user_id": req.UserID,
        "items":   req.Items,
    })
})

请求:

{
    "user_id": "123",
    "items": ["item1", "item2", "item3"]
}

Map 类型

r.POST("/config", func(c *gin.Context) {
    var config map[string]interface{}
    if err := c.ShouldBindJSON(&config); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    c.JSON(200, config)
})

指针字段

指针字段可以区分空值和未提供:

type User struct {
    Name  string  `json:"name"`
    Email *string `json:"email"` // nil 表示未提供
}

r.PATCH("/users/:id", func(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(400, gin.H{"error": err.Error()})
        return
    }

    if user.Email != nil {
        // 更新 email
    }

    c.JSON(200, user)
})

完整示例

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

type Address struct {
    City    string `json:"city"`
    Street  string `json:"street"`
    Country string `json:"country"`
}

type User struct {
    Name      string   `json:"name" binding:"required"`
    Email     string   `json:"email" binding:"required,email"`
    Age       int      `json:"age" binding:"gte=0,lte=150"`
    Tags      []string `json:"tags"`
    Address   Address  `json:"address"`
    Active    *bool    `json:"active"`
}

func main() {
    r := gin.Default()

    r.POST("/users", createUser)
    r.PATCH("/users/:id", updateUser)

    r.Run(":8080")
}

func createUser(c *gin.Context) {
    var user User
    if err := c.ShouldBindJSON(&user); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, gin.H{
        "message": "用户创建成功",
        "user":    user,
    })
}

func updateUser(c *gin.Context) {
    id := c.Param("id")

    var updates map[string]interface{}
    if err := c.ShouldBindJSON(&updates); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "id":      id,
        "updates": updates,
    })
}

小结

这一章学习了:

  • JSON 绑定的基本用法
  • JSON 标签选项
  • 嵌套结构体和数组
  • Map 和指针字段