Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

请求body为formdata,对应接收结构体存在嵌套继承时,参数绑定不上 #4

Closed
xmx opened this issue Nov 22, 2021 · 6 comments

Comments

@xmx
Copy link
Contributor

xmx commented Nov 22, 2021

go version:1.17.3
ship version:5.1.0

复现代码:

type person struct {
	Name string `json:"name" form:"name"`
	Age  int    `json:"age"  form:"age"`
}

type Student struct {
	person        // 这个结构体必须是首字母小写
	School string `json:"school" form:"school"`
	Grade  int    `json:"grade"  form:"grade"`
}

func Demo(c *ship.Context) error {
	var stu Student
	if err := c.Bind(&stu); err != nil {
		return ship.ErrBadRequest.New(err)
	}

	fmt.Println(stu.Name)   // 参数没有绑定上
	fmt.Println(stu.Age)    // 参数没有绑定上
	fmt.Println(stu.School) // OK
	fmt.Println(stu.Grade)  // OK

	return c.JSON(http.StatusOK, stu)
}

func main() {
	router := ship.Default()
	router.Route("/demo").POST(Demo)
	ship.StartServer(":9999", router)

	// $ go run main.go
	//
	// 请求 body 格式是 FormData:
	// $ curl -X POST -F "name=小明" -F "age=9" -F "school=新民小学" -F "grade=3" http://localhost:9999/demo
	//
	// 响应结果:
	// {"name":"","age":0,"school":"新民小学","grade":3}
}
@xgfone
Copy link
Owner

xgfone commented Nov 23, 2021

这是 Go 匿名嵌套 struct 用法错误。

type Student struct {
	person        // 这个结构体必须是首字母小写
	School string `json:"school" form:"school"`
	Grade  int    `json:"grade"  form:"grade"`
}

这种用法,属于匿名嵌套 struct,对于匿名嵌套,内层struct 仍是 外层 struct 的一个 Field,既然是 Field,那么它就有一个名字,该名字就是 内层 struct 类型的名字;对于上面的代码而言,就是 person。根据 Go语言规范,只有名字的首字母是大写的才能对外可见;对于上面的代码而言,名为 personField 是对外不可见的,因此,Bind 方法无法访问它,也就无法更新它的值,Bind 对其也就不成功(不是 Bind 有问题,而是 Go 语言规范或编译器所限制的)。 描述有误,不删除,作为记录。

解决这个问题有以下几个方法:

1、将 person 类型的名字改为 Person

type Person struct {
	Name string `json:"name" form:"name"`
	Age  int    `json:"age"  form:"age"`
}

type Student struct {
	Person           `json:"person"`
	School string   `json:"school" form:"school"`
	Grade  int      `json:"grade"  form:"grade"`
}

此时,匿名结构体 Field 的名字就变成 Person 了,因此,可以 Bind 它的值了。

2、明确写出 Field 的名字

如果结构体的类型名必须是小写字母开头,可以明确写出 Field 的名字,以代替匿名结构体,如下:

type person struct {
	Name string `json:"name" form:"name"`
	Age  int    `json:"age"  form:"age"`
}

type Student struct {
	Person persion  `json:"person"`
	School string `json:"school" form:"school"`
	Grade  int    `json:"grade"  form:"grade"`
}

其它说明: 如果既要内嵌结构体的类型名以小写字母开头,又要必须使用匿名结构体方式,那么这个匿名结构体 Field 就只能在当前包中才能访问,对于 Bind 功能,就要自己手动实现。

@xgfone
Copy link
Owner

xgfone commented Nov 23, 2021

上面的解释不周,对于 匿名嵌套 struct 的名字可见性 的描述部分有误,正确的是:当匿名 struct 自身的 Field 对外是可见时,不管匿名 struct 的类型自身是否是对外可见的,其对外可见的 Field 会自动变相地成为外层 struct 的对外可见的 Field

对于 Form 类型的 Bind,稍后改进,支持上述场景。

@xmx
Copy link
Contributor Author

xmx commented Nov 23, 2021

gingofiber 是可以正确的绑定成功

@xgfone
Copy link
Owner

xgfone commented Nov 23, 2021

gingofiber 是可以正确的绑定成功

是的,默认的 Form Bind实现(BindURLValues) 在处理上述情况下,仅判断了匿名嵌套结构体的可赋值性(即 reflect.Value.CanSet() ),未检查匿名嵌套结构体内部 Field 的可赋值性,所以才导致 Bind 未生效。这个稍后会修复。

@xgfone
Copy link
Owner

xgfone commented Nov 23, 2021

@xmx 已经修复。更新 版本到 v5.1.1 即可。如下:

// go.mod
module mypackage

require github.com/xgfone/ship/v5 v5.1.1

go 1.11
// main.go
package main

import (
	"fmt"
	"net/http"

	"github.com/xgfone/ship/v5"
)

type person struct {
	Name string `json:"name" form:"name"`
	Age  int    `json:"age"  form:"age"`
}

type Student struct {
	person        // 这个结构体必须是首字母小写
	School string `json:"school" form:"school"`
	Grade  int    `json:"grade"  form:"grade"`
}

func Demo(c *ship.Context) error {
	var stu Student
	if err := c.Bind(&stu); err != nil {
		return ship.ErrBadRequest.New(err)
	}

	fmt.Println(stu.Name)   // OK
	fmt.Println(stu.Age)    // OK
	fmt.Println(stu.School) // OK
	fmt.Println(stu.Grade)  // OK

	return c.JSON(http.StatusOK, stu)
}

func main() {
	router := ship.Default()
	router.Route("/demo").POST(Demo)
	ship.StartServer(":9999", router)

	// $ go run main.go
	//
	// 请求 body 格式是 FormData:
	// $ curl -X POST -F "name=小明" -F "age=9" -F "school=新民小学" -F "grade=3" http://localhost:9999/demo
	//
	// 响应结果:
	// {"name":"小明","age":9,"school":"新民小学","grade":3}
}

@xmx
Copy link
Contributor Author

xmx commented Nov 23, 2021

速度!

@xmx xmx closed this as completed Dec 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants