对象转换

对象转换在请求处理中非常常见。我们推荐将输入和输出定义为struct结构体对象,以便于结构化的参数输入输出维护。GoFrame框架支持非常便捷的对象转换,支持将客户端提交的参数如Query参数、表单参数、内容参数、JSON/XML等参数非常便捷地转换为指定的struct结构体,并且支持提交参数与struct属性的映射关系维护。

对象转换方法使用Request对象的Parse方法或者Get*Struct方法,具体方法定义请参考API文档: https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#Request

参数映射

默认规则

客户端提交的参数如果需要映射到服务端定义的struct属性上,可以采用默认的映射关系,这一点非常方便。默认的转换规则如下:

  1. struct中需要匹配的属性必须为公开属性(首字母大写)。
  2. 参数名称会自动按照 不区分大小写  忽略-/_/空格符号 的形式与struct属性进行匹配。
  3. 如果匹配成功,那么将键值赋值给属性,如果无法匹配,那么忽略该键值。

以下是几个匹配的示例:

map键名    struct属性     是否匹配
name       Name           match
Email      Email          match
nickname   NickName       match
NICKNAME   NickName       match
Nick-Name  NickName       match
nick_name  NickName       match
nick name  NickName       match
NickName   Nick_Name      match
Nick-name  Nick_Name      match
nick_name  Nick_Name      match
nick name  Nick_Name      match

由于底层对象转换实现使用的是gconv模块,因此也支持c/gconv/json标签,更详细的规则可以参考请参考 类型转换-Struct转换 章节。

自定义规则

请在对象属性与参数命长差异较大的业务场景下使用自定义映射规则,否则请使用默认的参数映射规则。因为大量的自定义规则标签会增加代码的维护成本。

自定义的参数映射规则可以通过为struct属性绑定tag实现,tag名称可以为p/param/params。例如:

type User struct{
    Id    int
    Name  string
    Pass1 string `p:"password1"`
    Pass2 string `p:"password2"`
}

其中我们使用了p标签来指定该属性绑定的参数名称。password1参数将会映射到Pass1属性,password2将会映射到Pass2属性上。其他属性采用默认的转换规则即可,无需设置tag

Parse转换

我们同时可以使用Parse方法来实现struct的转换,该方法是一个便捷方法,内部会自动进行转换及数据校验,但如果struct中没有校验tag的绑定将不会执行校验逻辑。

GoFrame v2版本开始,我们推荐使用结构化的方式来定义路由方法,更便捷地管理输入输出数据结构及其实例对象,具体请参考:路由注册-规范路由

使用示例:

package main

import (
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
)

type RegisterReq struct {
	Name  string
	Pass  string `p:"password1"`
	Pass2 string `p:"password2"`
}

type RegisterRes struct {
	Code  int         `json:"code"`
	Error string      `json:"error"`
	Data  interface{} `json:"data"`
}

func main() {
	s := g.Server()
	s.BindHandler("/register", func(r *ghttp.Request) {
		var req *RegisterReq
		if err := r.Parse(&req); err != nil {
			r.Response.WriteJsonExit(RegisterRes{
				Code:  1,
				Error: err.Error(),
			})
		}
		// ...
		r.Response.WriteJsonExit(RegisterRes{
			Data: req,
		})
	})
	s.SetPort(8199)
	s.Run()
}

在该示例中,我们定义了两个结构体:RegisterReq用于参数接收,RegisterRes用于数据返回。

其中,我们使用r.Parse(&req)将客户端提交的参数转换为RegisterReq对象,当转换成功时req变量将会被初始化赋值(默认情况下为nil),否则该方法返回err并且req变量为nil。返回数据结构通过RegisterRes定义,并且返回格式为JSON,通过r.Response.WriteJsonExit实现,该方法将RegisterRes根据其内部定义的json标签转换为JSON格式并退出当前服务方法,不再执行该服务方法的后续逻辑。

为了演示测试效果,这里在正常的返回结果Data属性中将RegisterReq对象返回,由于该对象没有绑定json标签,因此返回的JSON字段将会为其属性名称。

执行后,我们通过curl工具来测试一下:

$ curl "http://127.0.0.1:8199/register?name=john&password1=123&password2=456"
{"code":0,"error":"","data":{"Name":"john","Pass":"123","Pass2":"456"}}

$ curl -d "name=john&password1=123&password2=456" -X POST "http://127.0.0.1:8199/register"
{"code":0,"error":"","data":{"Name":"john","Pass":"123","Pass2":"456"}}

我们使用了GETPOST两种提交方式来做测试,可以看到服务端都能完美地获取到提交参数并完成对象转换。



Content Menu

  • No labels

9 Comments

  1. 不好意思,刚才的问题问得不清楚。

    type StructId struct {
    Id string `json:"jti,omitempty"` //这个表达方法好像不起作用
    }

    s := g.Server()
    s.BindHandler("/", func(r *ghttp.Request) {
    ab := &StructId{}
    r.Parse(ab)
    fmt.Println(ab.Id) //I expect "2" but got ""
    })

    res := httptest.NewRecorder()
    req := httptest.NewRequest(
    "POST",
    "/",
    strings.NewReader(`{"jti":"2"}`),
    )
    s.ServeHTTP(res, req)


    1. 没看懂你的问题。能描述清楚吗?

  2. Tan Sia How 认真看文档, 你要写成下面这样才可以:

    type StructId struct {
    Id string `p:"jti"`
    }
  3. 结构体设置标签时,我发现同时有p和json标签的情况下,请求参数名称既可以写p标签的形式,也可以写结构体原本参数的名字,而json标签不起作用;
    没有P却有json的情况下,请求参数名称既可以写json标签的形式,也可以写结构体原本参数的名字

    1. 嗯,  我也遇到了.  我是想设置了p标签后, 就只根据p标签生效, 但实际效果不是这样的.

      而且有时得到的是p标签的值, 有时得到的是结构体字段的值. 很疑惑, 求解释

      1. 参数映射

        默认规则

        客户端提交的参数如果需要映射到服务端定义的struct属性上,可以采用默认的映射关系,这一点非常方便。默认的转换规则如下:

        1. struct中需要匹配的属性必须为公开属性(首字母大写)。
        2. 参数名称会自动按照 不区分大小写  忽略-/_/空格符号 的形式与struct属性进行匹配。
        3. 如果匹配成功,那么将键值赋值给属性,如果无法匹配,那么忽略该键值。


        文章里不是说了么? request请求的参数会自动按照 不区分大小写  忽略-/_/空格符号 的形式与struct属性进行匹配,所以,你参数大小写是会被忽略的。另外,json标签是struct转json也就是 Json.Marshal()生效用的,最后展现形式一方面是代码里用,另一方面会展示在swagger上,比如,你加了json的标签,swagger会展示成json指定的字段,否则展示的就是结构体的字段。

  4. @郭强,不知道这里你能否看到,我有一个疑问,我们目前的返回统一是这样的格式,

    type Response struct {
    	Code  int         `json:"code"`
    	Msg   string      `json:"msg"`
    	Data  interface{} `json:"data"`
    }

    按照现在的编程规范,是不是每个接口返回,都要写一遍,例如,上面的这个注册响应,写成下面的这样,

    type RegisterRes struct {
    	Code  int         `json:"code"`
    	Msg   string      `json:"msg"`
    	Data  interface{} `json:"data"`
    }

    希望您看到后解惑!

    1. 统一返回格式,可以使用全局中间件,不用每个接口返回都写一遍。比如:

      s.Use(
      		service.Middleware().MiddlewareCORS,
      		service.Middleware().Ctx,
      		service.Middleware().ResponseHandler,
      	)
      func (s *sMiddleware) ResponseHandler(r *ghttp.Request) {
      
      	// 进行格式化代码
      }

      也可以参考 gf仓库focus  gogf/focus-single: Single repo demo project using GoFrame. (github.com) 

      1. 明白了,谢谢!