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

BindAndValidate BindForm解析时间字符串为空 #1123

Open
wujianbo00 opened this issue May 24, 2024 · 3 comments
Open

BindAndValidate BindForm解析时间字符串为空 #1123

wujianbo00 opened this issue May 24, 2024 · 3 comments

Comments

@wujianbo00
Copy link

Describe the bug

v0.7.0版本之后 BindAndValidate BindForm不能正确解析时间字符串

To Reproduce

Steps to reproduce the behavior:

  1. 代码片段
    type MyTime time.Time
    type ReqTime struct {
    Birthday MyTime json:"birth" form:"birth" //MyTime已经重写UnmarshalJSON解析字符串
    }
    func Test(ctx context.Context, c *app.RequestContext) {
    var req ReqTime
    c.BindAndValidate(&req) // v0.9.0 Birthday是空的。换成v0.6.2就正常了
    c.BindForm(&req) // v0.9.0 Birthday也是空的
    }
  2. 请求curl -X 'POST' 'http://localhost:8080/test' -H 'accept: application/json' -H 'Content-Type: application/x-www-form-urlencoded' -d 'birth=2024-01-01%2012%3A00%3A00'

Expected behavior
req.Birthday不应该是空值

Screenshots

Hertz version:
v0.7.0 or above

Environment:

Additional context

@FGYFFFF
Copy link
Contributor

FGYFFFF commented May 24, 2024

现在先用json 来规避吧

@FGYFFFF
Copy link
Contributor

FGYFFFF commented May 27, 2024

我测试了下,如果你的 "MyTime已经重写UnmarshalJSON解析字符串" 是会正常进行参数绑定的;如果你稳定复现的话,可以粘贴一个具体的单测例子。 以下是我的测试 case

type MyTime time.Time

func (m *MyTime) UnmarshalJSON(b []byte) error {
	t := time.Now().AddDate(0, 0, -2)
	*m = MyTime(t)
	return nil
}

type ReqTime struct {
	Birthday MyTime `json:"birth" form:"birth"` //MyTime已经重写UnmarshalJSON解析字符串
}

func TestTime(t *testing.T) {

	r := protocol.NewRequest("POST", "/foo", nil)
	now := time.Now().UTC()
	fmt.Println(now.String())
	r.SetRequestURI(fmt.Sprintf("/foo"))
	r.Header.SetContentTypeBytes([]byte("application/x-www-form-urlencoded"))
	kv := url.Values{}
	kv.Set("birth", now.String())
	r.SetBody([]byte(kv.Encode()))

	var req ReqTime
	err := DefaultBinder().Bind(r, &req, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
		return
	}
}

@AiQiYang
Copy link

AiQiYang commented Jan 2, 2025

我测试了一下,在使用复杂的自定义类型时,像time.Time,需要明确知道如何将请求参数(如字符串)转换为目标类型。就需要设置自定义的BindConfig,并注册自定义解码器。这样子就可以使用自定义的类型了。测试代码如下

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"reflect"
	"time"

	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/utils"
	"github.com/cloudwego/hertz/pkg/protocol"
	"github.com/cloudwego/hertz/pkg/protocol/consts"
	"github.com/cloudwego/hertz/pkg/route/param"
	"github.com/cloudwego/hertz/pkg/app/server/binding"
)

// 自定义类型
type Mytime time.Time

// 实现 MarshalJSON 方法,用于 JSON 序列化
func (mt Mytime) MarshalJSON() ([]byte, error) {
	return json.Marshal(time.Time(mt).Format(time.RFC3339))
}

// 实现 UnmarshalJSON 方法,用于 JSON 反序列化
func (mt *Mytime) UnmarshalJSON(data []byte) error {
	var timeStr string
	if err := json.Unmarshal(data, &timeStr); err != nil {
		return err
	}
	t, err := time.Parse(time.RFC3339, timeStr)
	if err != nil {
		return err
	}
	*mt = Mytime(t)
	return nil
}

// 自定义解码函数
func decodeMytime(req *protocol.Request, params param.Params, text string) (reflect.Value, error) {
	if text == "" {
		return reflect.ValueOf(Mytime(time.Time{})), nil
	}
	t, err := time.Parse(time.RFC3339, text)
	if err != nil {
		return reflect.Value{}, err
	}
	return reflect.ValueOf(Mytime(t)), nil
}

func main() {
	// 创建 BindConfig 并注册 Mytime 的自定义解码器
	config := binding.NewBindConfig() // 使用 binding.NewBindConfig
	config.MustRegTypeUnmarshal(reflect.TypeOf(Mytime{}), decodeMytime)

	// 创建 Hertz 服务器,并设置自定义 BindConfig
	r := server.New(
		server.WithBindConfig(config), // 设置自定义 BindConfig
	)

	// 注册路由
	r.POST("/hello", func(ctx context.Context, c *app.RequestContext) {
		// 定义请求结构体
		type ReqTime struct {
			Birthday Mytime `form:"birth"` // 使用 form 标签
		}

		// BindAndValidate
		var req ReqTime
		err := c.BindAndValidate(&req)
		if err != nil {
			fmt.Println("参数绑定失败:", err)
			c.JSON(consts.StatusInternalServerError, utils.H{"message": "参数绑定失败"})
			return
		}

		// 使用绑定和验证后的数据
		fmt.Println("Received 'birth':", time.Time(req.Birthday))
		c.JSON(consts.StatusOK, utils.H{"message": "成功", "birth": time.Time(req.Birthday)})
	})

	r.Spin()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

3 participants