JSON 响应

现在的 API 开发,JSON 已经是事实上的标准数据交换格式了。Gin 返回 JSON 响应非常简单,而且提供了好几种方式来满足不同的需求。

基本的 JSON 响应

最常用的就是 c.JSON() 方法,传入状态码和数据就行:

package main

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

func main() {
    r := gin.Default()
    
    r.GET("/user", func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{
            "name": "张三",
            "age":  25,
            "city": "北京",
        })
    })
    
    r.GET("/users", func(c *gin.Context) {
        users := []gin.H{
            {"name": "张三", "age": 25},
            {"name": "李四", "age": 30},
        }
        c.JSON(http.StatusOK, users)
    })
    
    r.Run(":8080")
}

gin.H 是一个很方便的快捷方式,本质上是 map[string]interface{},用来快速构建 JSON 对象。

使用结构体返回 JSON

实际项目中,我们更多时候会用结构体来定义响应数据:

type UserResponse struct {
    ID      int    `json:"id"`
    Name    string `json:"name"`
    Email   string `json:"email"`
    IsAdmin bool   `json:"is_admin"`
}

type ApiResponse struct {
    Code    int         `json:"code"`
    Message string      `json:"message"`
    Data    interface{} `json:"data"`
}

r.GET("/profile", func(c *gin.Context) {
    user := UserResponse{
        ID:      1,
        Name:    "张三",
        Email:   "zhangsan@example.com",
        IsAdmin: false,
    }
    
    c.JSON(http.StatusOK, ApiResponse{
        Code:    0,
        Message: "success",
        Data:    user,
    })
})

结构体的好处是类型安全,而且可以通过 json tag 控制字段名和序列化行为。

格式化输出

调试的时候,格式化的 JSON 更容易阅读。Gin 提供了 IndentedJSON 方法:

r.GET("/pretty", func(c *gin.Context) {
    c.IndentedJSON(http.StatusOK, gin.H{
        "message": "这是格式化的 JSON",
        "data": gin.H{
            "items": []string{"a", "b", "c"},
        },
    })
})

不过要注意,生产环境不建议用这个,因为会增加响应体积。

安全的 JSON 响应

有时候我们需要防止 JSON 劫持,可以用 SecureJSON

r.GET("/secure", func(c *gin.Context) {
    c.SecureJSON(http.StatusOK, []gin.H{
        {"name": "张三"},
        {"name": "李四"},
    })
})

这会在响应前面加上 while(1);,防止恶意网站通过 script 标签获取你的 JSON 数据。

JSONP 支持

如果你的 API 需要支持跨域的 JSONP 请求:

r.GET("/jsonp", func(c *gin.Context) {
    c.JSONP(http.StatusOK, gin.H{
        "message": "JSONP 响应",
    })
})

访问 /jsonp?callback=myCallback,会返回 myCallback({"message":"JSONP 响应"})

AsciiJSON

如果需要确保 JSON 中的非 ASCII 字符被转义:

r.GET("/ascii", func(c *gin.Context) {
    c.AsciiJSON(http.StatusOK, gin.H{
        "name": "张三",
        "city": "北京",
    })
})

返回的结果会是 {"name":"\u5f20\u4e09","city":"\u5317\u4eac"}

PureJSON

默认情况下,Go 的 JSON 序列化会把 <>& 这些 HTML 特殊字符转义。如果你不想转义:

r.GET("/pure", func(c *gin.Context) {
    c.PureJSON(http.StatusOK, gin.H{
        "html": "<b>这是纯文本 HTML</b>",
    })
})

这样 <> 就不会被转义成 \u003c\u003e 了。

小结

Gin 提供了丰富的 JSON 响应方法,日常开发中 c.JSON() 基本够用。遇到特殊场景,比如调试、安全防护、跨域支持,可以选择对应的方法。记住,生产环境尽量用紧凑的 JSON 格式,减少网络传输量。