gin: Cannot Parse time with time_format set from JSON

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!

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 7
  • Comments: 15 (4 by maintainers)

Most upvoted comments

I still have this issue (bug)

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!

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"`
}