Gin 框架綁定 JSON 參數使用 jsoniter

Gin 框架中,處理 JSON 格式的參數綁定時,默認採用的標準包 encoding/json,然而標準包不能知足咱們的一些要求,好比兼容字符串整型、PHP空數組、時間格式等。git

最簡單的方式

開發 API 時,須要用到 ShouldBindJSON 綁定傳入的參數到結構體:github

// 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 包:json

// 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
)

同時咱們看到還支持了 jsonitersegmentfault

// 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:數組

// +build jsoniter

因此,咱們只需在編譯時帶上這個 tag 就能夠了,例如:框架

go build -tags=jsoniter main.go

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

自定義的方式

Gin 框架支持的 jsoniter 是默認配置 jsoniter.ConfigCompatibleWithStandardLibrary。當咱們須要其餘配置或添加一些自定義擴展(好比時間處理)時,就難受了。因而咱們就要本身動手了~post

翻開源碼,咱們能看到 binding.JSON 其實使用的是 jsonBinding{} 這個結構體:ui

// 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 源碼看看:.net

// 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 這個接口:插件

// 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
}

那接下來就簡單了,咱們只要實現了這個接口便可,例如:

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種方式:

// binding.JSON 替換成自定義的
ctx.ShouldBindWith(&params, binding.JSON)
ctx.ShouldBindBodyWith(&params, binding.JSON)

上述自定義的方式,還能夠用於其餘包,不只限於 iterator。從這個方面體現出了 Gin 框架良好的接口設計👍


感謝您的閱讀,以爲內容不錯,點個贊吧 😆
原文地址: https://shockerli.net/post/gin-binding-jsoniter/
相關文章
相關標籤/搜索