Gin 框架中,处理 JSON 格式的参数绑定时,默认采用的标准包 encoding/json,然而标准包不能满足我们的一些要求,比如兼容字符串整型、PHP空数组、时间格式等。

最简单的方式

开发 API 时,需要用到 ShouldBindJSON 绑定传入的参数到结构体:

1
2
3
4
5
6
// github.com/gin-gonic/gin@v1.6.3/context.go:643

// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
func (c *Context) ShouldBindJSON(obj interface{}) error {
	return c.ShouldBindWith(obj, binding.JSON)
}

Gin 默认采用 encoding/json 包:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// github.com/gin-gonic/gin@v1.6.3/internal/json/json.go
// +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
)

同时我们看到还支持了 jsoniter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// github.com/gin-gonic/gin@v1.6.3/internal/json/jsoniter.go
// +build jsoniter

package json

import "github.com/json-iterator/go"

var (
	json = jsoniter.ConfigCompatibleWithStandardLibrary
	// 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
)

那我们怎么才能使用到 jsoniter 呢?源码中已经明确了编译tag:

1
// +build jsoniter

所以,我们只需在编译时带上这个 tag 就可以了,例如:

1
2
3
4
go build -tags=jsoniter main.go

// 或者
go run -tags=jsoniter main.go

自定义的方式

Gin 框架支持的 jsoniter 是默认配置 jsoniter.ConfigCompatibleWithStandardLibrary。当我们需要其他配置或添加一些自定义扩展(比如时间处理)时,就难受了。于是我们就要自己动手了~

翻开源码,我们能看到 binding.JSON 其实使用的是 jsonBinding{} 这个结构体:

1
2
3
4
5
6
7
8
// github.com/gin-gonic/gin@v1.6.3/binding/binding.go:73

// These implement the Binding interface and can be used to bind the data
// present in the request to struct instances.
var (
	JSON          = jsonBinding{}
	// 其他省略了...
)

翻开 jsonBinding 源码看看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// github.com/gin-gonic/gin@v1.6.3/binding/json.go

type jsonBinding struct{}

func (jsonBinding) Name() string {
	return "json"
}

func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
	if req == nil || req.Body == nil {
		return fmt.Errorf("invalid request")
	}
	return decodeJSON(req.Body, obj)
}

func (jsonBinding) BindBody(body []byte, obj interface{}) error {
	return decodeJSON(bytes.NewReader(body), obj)
}

发现实现了 BindingBody 这个接口:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// github.com/gin-gonic/gin@v1.6.3/binding/binding.go:36

// Binding describes the interface which needs to be implemented for binding the
// data present in the request such as JSON request body, query parameters or
// the form POST.
type Binding interface {
	Name() string
	Bind(*http.Request, interface{}) error
}

// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
// but it reads the body from supplied bytes instead of req.Body.
type BindingBody interface {
	Binding
	BindBody([]byte, interface{}) error
}

那接下来就简单了,我们只要实现了这个接口即可,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package custom

import (
	"bytes"
	"fmt"
	"io"
	"net/http"

	jsoniter "github.com/json-iterator/go"

	"github.com/gin-gonic/gin/binding"
)

// BindingJSON 替换Gin默认的binding,支持更丰富JSON功能
var BindingJSON = jsonBinding{}

// 可以自定义jsoniter配置或者添加插件
var json = jsoniter.ConfigCompatibleWithStandardLibrary

type jsonBinding struct{}

func (jsonBinding) Name() string {
	return "json"
}

func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
	if req == nil || req.Body == nil {
		return fmt.Errorf("invalid request")
	}
	return decodeJSON(req.Body, obj)
}

func (jsonBinding) BindBody(body []byte, obj interface{}) error {
	return decodeJSON(bytes.NewReader(body), obj)
}

func decodeJSON(r io.Reader, obj interface{}) error {
	decoder := json.NewDecoder(r)
	if binding.EnableDecoderUseNumber {
		decoder.UseNumber()
	}
	if binding.EnableDecoderDisallowUnknownFields {
		decoder.DisallowUnknownFields()
	}
	if err := decoder.Decode(obj); err != nil {
		return err
	}
	return validate(obj)
}

func validate(obj interface{}) error {
	if binding.Validator == nil {
		return nil
	}
	return binding.Validator.ValidateStruct(obj)
}

自定义 jsonBinding 已经写好了,可使用有2种方式:

1
2
3
// binding.JSON 替换成自定义的
ctx.ShouldBindWith(&params, binding.JSON)
ctx.ShouldBindBodyWith(&params, binding.JSON)

上述自定义的方式,还可以用于其他包,不仅限于 iterator。从这个方面体现出了 Gin 框架良好的接口设计👍