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 uint json:"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 uint json:"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 uint json:"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