Gin源码分析 – Context之渲染(JSON)

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
)