13

Gin 框架绑定 JSON 参数使用 jsoniter

 3 years ago
source link: https://shockerli.net/post/gin-binding-jsoniter/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

最简单的方式

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

1
2
3
4
5
6
// github.com/gin-gonic/[email protected]/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/[email protected]/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/[email protected]/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/[email protected]/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/[email protected]/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/[email protected]/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 框架良好的接口设计👍


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK