Gin源码分析 – Context之渲染(JSON)
•
Gin框架
1 介绍
首先用一个例子,介绍一下相关函数的用法,然后通过源码分析一下具体的实现方法。
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() r.GET("/jsonmap", func(c *gin.Context) { data := map[string]interface{}{ "name": "TerryPro", "age": 18, "height": "180CM", } c.JSON(http.StatusOK, data) }) r.GET("/jsonstruct", func(c *gin.Context) { type userInfo struct { Name string `json:"name"` Age int `json:"age"` Height string `json:"height"` } user := userInfo{ "TerryPro", 18, "185CM", } c.JSON(http.StatusOK, user) }) r.Run() }
Gin支持两种JSON的渲染方法,
- 将map渲染成JSON,这种方面直接将key,value转化成JSON;
- 将struct渲染成JSON,这种方面可以通过struct字段的注解,进行更多的控制。
针对上面的例子,采用如下的命令行进行测试
D:\Developer\go-dev\go-gin-example>curl http://127.0.0.1:8080/jsonmap {"age":18,"height":"180CM","name":"TerryPro" D:\Developer\go-dev\go-gin-example>curl http://127.0.0.1:8080/jsonstruct {"name":"TerryPro","age":18,"height":"185CM"}
2 分析
// JSON serializes the given struct as JSON into the response body. // It also sets the Content-Type as "application/json". func (c *Context) JSON(code int, obj interface{}) { c.Render(code, render.JSON{Data: obj}) } // JSON contains the given interface object. type JSON struct { Data interface{} }
Context的JSON方法,调用Context的Render方法进行渲染,其中涉及的接口,结构体如下:
2.1 Render接口
这个接口是用于渲染结果实现这个接口的结构体包括:render.JSON,render.XML等,相关代码如下。
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on. type Render interface { // Render writes data with custom ContentType. Render(http.ResponseWriter) error // WriteContentType writes custom ContentType. WriteContentType(w http.ResponseWriter) } // 通过下面的语句,确保JSON、XML、HTML、YAML等结构体实现了Render接口 var ( _ Render = JSON{} _ Render = IndentedJSON{} _ Render = SecureJSON{} _ Render = JsonpJSON{} _ Render = XML{} _ Render = String{} _ Render = Redirect{} _ Render = Data{} _ Render = HTML{} _ HTMLRender = HTMLDebug{} _ HTMLRender = HTMLProduction{} _ Render = YAML{} _ Render = Reader{} _ Render = AsciiJSON{} _ Render = ProtoBuf{} )
2.2 Context.Render方法
// Render writes the response headers and calls render.Render to render data. func (c *Context) Render(code int, r render.Render) { // 设置状态 c.Status(code) ... // 调用Render接口的Writer方法 if err := r.Render(c.Writer); err != nil { panic(err) } }
该方法首先设置HTTP返回码,然后调用Render子类的Render方法,此处就是JSON的Render方法,分析如下。
2.3 JSON.Render方法
// Render (JSON) writes data with custom ContentType. func (r JSON) Render(w http.ResponseWriter) (err error) { if err = WriteJSON(w, r.Data); err != nil { panic(err) } return } var jsonContentType = []string{"application/json; charset=utf-8"} // WriteJSON marshals the given interface object and writes it with custom ContentType. func WriteJSON(w http.ResponseWriter, obj interface{}) error { writeContentType(w, jsonContentType) jsonBytes, err := json.Marshal(obj) if err != nil { return err } _, err = w.Write(jsonBytes) return err } func writeContentType(w http.ResponseWriter, value []string) { header := w.Header() if val := header["Content-Type"]; len(val) == 0 { header["Content-Type"] = value } }
(1)调用WriteJSON,完成具体的渲染工作;
(2)调用writeContentType,写入ContextType;
(3)调用Marshal完成具体的JSON序列化,Marshal默认采用了encoding/json进行渲染,参见如下代码,实际根据需要也可以使用其它的JSON包进行。
//go:build !jsoniter // +build !jsoniter package json import "encoding/json" var ( // Marshal is exported by gin/json package. Marshal = json.Marshal // Unmarshal is exported by gin/json package. Unmarshal = json.Unmarshal // MarshalIndent is exported by gin/json package. MarshalIndent = json.MarshalIndent // NewDecoder is exported by gin/json package. NewDecoder = json.NewDecoder // NewEncoder is exported by gin/json package. NewEncoder = json.NewEncoder )