Problem: I can't parse a time string even when setting the time_format value. Here is my struct,
type Class struct {
StartAt time.Time `json:"start_at" time_format:"2006-01-02" time_utc:"1" binding:"required"`
ChallengeID uint `json:"challenge_id" gorm:"index" binding:"required"`
}
and the request I am making of which I immediately call c.ShouldBindJSON(&class)
,
POST /my_endpoint HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 9fe5bfbf-8087-6c48-b30e-c58ec87fceb0
{
"challenge_id": 1,
"start_at": "2017-01-08"
}
Regardless of sending the correct format, I always get this error back,
"parsing time \"\"2017-01-08\"\" as \"\"2006-01-02T15:04:05Z07:00\"\": cannot parse \"\"\" as \"T\""
Any help is appreciated. Thanks!
Comment From: mlund01
Figured out the answer. Has nothing to do with gin but what the golang JSON Decoder expects for time values. It must be in the format of RFC3339 in order to be successfully decoded when using the JSON binder for ShouldBindJSON
, or BindJSON
.
However, it should be addressed that this creates an inconsistency when binding from form data vs binding from JSON data. When binding form data, if you set the time_format
tag and try to send anything other than the time format you specify, it will fail, but with JSON it totally ignores the time_format and throws an error if it is not in RFC3339 format.
If not fixed in gin, this should at least be addressed in the docs. I'd be happy to make a PR!
Comment From: NarHakobyan
I still have this issue (bug)
Comment From: sheltonsuen
@NarHakobyan How did you solve this problem?
Comment From: vkd
The gin use the default golang json unmarshaler, where you can use a custom type to unmarshal a value as you want. Example:
type myTime time.Time
var _ json.Unmarshaler = &myTime{}
func (mt *myTime) UnmarshalJSON(bs []byte) error {
var s string
err := json.Unmarshal(bs, &s)
if err != nil {
return err
}
t, err := time.ParseInLocation("2006-01-02", s, time.UTC)
if err != nil {
return err
}
*mt = myTime(t)
return nil
}
type Class struct {
StartAt myTime `json:"start_at" binding:"required"`
ChallengeID uint `json:"challenge_id" gorm:"index" binding:"required"`
}
Comment From: mannixli
@NarHakobyan How did you solve this problem? use time format "2017-01-08T00:00:00Z" https://github.com/gin-gonic/gin/issues/1062#issuecomment-325188363
Comment From: archiewx
The gin use the default golang json unmarshaler, where you can use a custom type to unmarshal a value as you want. Example:
``` type myTime time.Time
var _ json.Unmarshaler = &myTime{}
func (mt myTime) UnmarshalJSON(bs []byte) error { var s string err := json.Unmarshal(bs, &s) if err != nil { return err } t, err := time.ParseInLocation("2006-01-02", s, time.UTC) if err != nil { return err } mt = myTime(t) return nil }
type Class struct { StartAt myTime
json:"start_at" binding:"required"
ChallengeID uintjson:"challenge_id" gorm:"index" binding:"required"
} ```
Using the custom type to unmarshal a value that casing the sql can't convert the type.
what should I do? @vkd
Comment From: vkd
@zsirfs Could you please expand your question with more details? What does the request look like? What is your expectation?
Comment From: alejandrolorefice
The gin use the default golang json unmarshaler, where you can use a custom type to unmarshal a value as you want. Example: ``` type myTime time.Time
var _ json.Unmarshaler = &myTime{}
func (mt myTime) UnmarshalJSON(bs []byte) error { var s string err := json.Unmarshal(bs, &s) if err != nil { return err } t, err := time.ParseInLocation("2006-01-02", s, time.UTC) if err != nil { return err } mt = myTime(t) return nil }
type Class struct { StartAt myTime
json:"start_at" binding:"required"
ChallengeID uintjson:"challenge_id" gorm:"index" binding:"required"
} ```Using the custom type to unmarshal a value that casing the sql can't convert the type.
what should I do? @vkd
If you need sql to be able to handle your custom type you must implement Scanner and Valuer interfaces for the type as shown in this example
Comment From: ahhmarr
The gin use the default golang json unmarshaler, where you can use a custom type to unmarshal a value as you want. Example:
``` type myTime time.Time
var _ json.Unmarshaler = &myTime{}
func (mt myTime) UnmarshalJSON(bs []byte) error { var s string err := json.Unmarshal(bs, &s) if err != nil { return err } t, err := time.ParseInLocation("2006-01-02", s, time.UTC) if err != nil { return err } mt = myTime(t) return nil }
type Class struct { StartAt myTime
json:"start_at" binding:"required"
ChallengeID uintjson:"challenge_id" gorm:"index" binding:"required"
} ```
can i "unmarshal" form:"field"
value or does it needs to be json:"field"
case in point
type Student struct {
ID uint32 `form:"id"`
Dob *Date `form:"dob" `
Name string `form:"name"`
}
and I've custom marshalar Date
as
type Date time.Time
var _ json.Unmarshaler = &Date{}
const dateFormat = "2006-01-02"
func (mt *Date) UnmarshalJSON(bs []byte) error {
var s string
err := json.Unmarshal(bs, &s)
if err != nil {
return err
}
t, err := time.ParseInLocation(dateFormat, s, time.UTC)
if err != nil {
return err
}
*mt = Date(t)
return nil
}
func (mt *Date) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(*mt).Format(dateFormat))
}
it works if tag is json:"dob"
doesn't works if tag is form:"dob"
@vkd
Comment From: vkd
@ahhmarr this should work for you:
type Student struct {
ID uint32 `form:"id"`
Dob *Date `form:"dob" json:"dob"`
Name string `form:"name"`
}
Comment From: ahhmarr
thnx for the reply @vkd
Actually, I'm trying to send multipart data (an image in the payload as well) sample request
POST /api/v1/endpoint/ HTTP/1.1
Host: some host
Content-Length: 1083
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="dob"
1998-10-10
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="image"; filename="file.png"
Content-Type: image/png
(data)
have the struct as
type Student struct {
ID uint32 `form:"id"`
Dob *Date `form:"dob" json:"dob"`
Name string `form:"name"`
}
i get error when i try to bind validation smth like
if err := ctx.ShouldBind(&studentData); err != nil {
log.Printf(" %v", err) // -> invalid character '-' after top-level value
return
}
Comment From: vkd
Ok, I see. Could you try this:
type Student struct {
ID uint32 `form:"id"`
Dob *time.Time `form:"dob" time_format:"2006-01-02"`
Name string `form:"name"`
}
The point is the form binding is not calling Date.UnmarshalJSON(...)
function, but gin fortunately has special case for the time.Time
parsing - the time_format
tag: https://github.com/gin-gonic/gin/blob/master/binding/form_mapping.go#L281
Comment From: dgqypl
refer to this: https://segmentfault.com/a/1190000022264001
Comment From: warjiang
refer to this: https://segmentfault.com/a/1190000022264001
nice, a elegant solution~~
Comment From: chjiyun
@vkd iso time can pass the json decode, I have made a test to it