gin源码阅读(2)请求体中的JSON参数是如何解析的?

gin源码阅读(2)请求体中的JSON参数是如何解析的?

这篇文章中我们会来关注一下gin是如何解析请求体中的json参数的。

绑定请求体数据的四种方式

Gin 提供了四种可直接将请求体中的 JSON 数据解析并绑定至相应类型的函数,分别是:BindJSON, Bind, ShouldBindJSON, ShouldBind。下面讲解的不会太涉及具体的 JSON 解析算法,而是更偏向于 Gin 内部的实现逻辑。

其中,可为它们分为两类,一类为 Must Bind,另一类为 Should Bind,前缀为 Should 的皆属于 Should Bind,而以 Bind 为前缀的,则属于 Must Bind。正如其名,Must Bind 一类在对请求进行解析时,若出现错误,会通过 c.AbortWithError(400, err).SetType(ErrorTypeBind) 终止请求,这会把响应码设置为 400,Content-Type 设置为 text/plain; charset=utf-8,在此之后,若尝试重新设置响应码,则会出现警告,如将响应码设置为 200:[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 200;而 Should Bind 一类在对请求进行解析时,若出现错误,只会将错误返回,而不会主动进行响应,所以,在使过程中,如果对产生解析错误的行为有更好的控制,最好使用 Should Bind 一类,自行对错误行为进行处理。

我们先来看一下开始提到的 BindBindJSON 这两个函数的实现源代码:

go 复制代码
// gin v1.10.0 context.go

// Bind checks the Method and Content-Type to select a binding engine automatically,
// Depending on the "Content-Type" header different bindings are used, for example:
//
//	"application/json" --> JSON binding
//	"application/xml"  --> XML binding
//
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
// It decodes the json payload into the struct specified as a pointer.
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
func (c *Context) Bind(obj any) error {
	b := binding.Default(c.Request.Method, c.ContentType())
	return c.MustBindWith(obj, b)
}

// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) BindJSON(obj any) error {
	return c.MustBindWith(obj, binding.JSON)
}

// MustBindWith binds the passed struct pointer using the specified binding engine.
// It will abort the request with HTTP 400 if any error occurs.
// See the binding package.
func (c *Context) MustBindWith(obj any, b binding.Binding) error {
	if err := c.ShouldBindWith(obj, b); err != nil {
		c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
		return err
	}
	return nil
}

从上面的源码可以看到BindBindJSON其实调用的都是MustBindWith,而MustBindWith内部调用的又是ShouldBindWith函数。

常用的绑定JSON请求数据的ShouldBindJSON方法

从下面的源码中可以看到,常用的ShouldBindJSON(...)方法也是调用的ShouldBindWith方法。

go 复制代码
// gin v1.10.0 context.go

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

接下来我们就详细看看ShouldBindWith方法内部是怎么实现的?代码如下所示:

go 复制代码
// gin v1.10.0 context.go

// ShouldBindWith binds the passed struct pointer using the specified binding engine.
// See the binding package.
func (c *Context) ShouldBindWith(obj any, b binding.Binding) error {
	return b.Bind(c.Request, obj)
}

ShouldBindWith方法调用了接口binding.BindingBind(...)方法,我们先看一下这个接口的定义:

go 复制代码
// gin v1.10.0 binding/binding.go

// 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, any) error
}

然后我们接着看一下ShouldBindJSON(...)方法中传递的binding.JSON,如下所示:

go 复制代码
// gin v1.10.0 binding/binding.go

// These implement the Binding interface and can be used to bind the data
// present in the request to struct instances.
var (
	JSON          BindingBody = jsonBinding{}
	XML           BindingBody = xmlBinding{}
	Form          Binding     = formBinding{}
	Query         Binding     = queryBinding{}
	FormPost      Binding     = formPostBinding{}
	FormMultipart Binding     = formMultipartBinding{}
	ProtoBuf      BindingBody = protobufBinding{}
	MsgPack       BindingBody = msgpackBinding{}
	YAML          BindingBody = yamlBinding{}
	Uri           BindingUri  = uriBinding{}
	Header        Binding     = headerBinding{}
	TOML          BindingBody = tomlBinding{}
)

可以看到在gin中定义了很多的binding.Binding接口的实现,其中我们重点关注jsonBinding类型,代码如下:

go 复制代码
// gin v1.10.0 binding/json.go

type jsonBinding struct{}

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

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

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

简单总结一下上述代码,就是调用golang自带库的json包工具将请求体中的json数据解析成结构体数据。

总结

gin中绑定请求体中数据的方法有两大类共四个方法,分别是强制绑定的Bind(...)BindJson(...),以及非强制绑定的ShouldBind(...)ShouldBindJson(...)

我们详细分析了ShouldBindJson(...)方法,它的内部就是调用go自带库中的json包进行JSON请求数据的解析。

参考文章:

1、Gin 源码学习(二)丨请求体中的参数是如何解析的?

相关推荐
恋猫de小郭3 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅10 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅11 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅12 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊12 小时前
jwt介绍
前端