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

Cannot Parse time with time_format set from JSON #1193

Closed
mlund01 opened this issue Dec 7, 2017 · 15 comments
Closed

Cannot Parse time with time_format set from JSON #1193

mlund01 opened this issue Dec 7, 2017 · 15 comments

Comments

@mlund01
Copy link

mlund01 commented Dec 7, 2017

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!

@mlund01
Copy link
Author

mlund01 commented Dec 9, 2017

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!

@mlund01 mlund01 changed the title Cannot Parse time with time_format set Cannot Parse time with time_format set from JSON Dec 9, 2017
@mlund01 mlund01 closed this as completed Jan 14, 2018
@NarHakobyan
Copy link

I still have this issue (bug)

@sheltonsuen
Copy link

@NarHakobyan How did you solve this problem?

@vkd
Copy link
Contributor

vkd commented Jun 30, 2019

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

@mannixli
Copy link

@NarHakobyan How did you solve this problem?
use time format "2017-01-08T00:00:00Z"
#1062 (comment)

@archiewx
Copy link

archiewx commented Jan 6, 2020

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

@vkd
Copy link
Contributor

vkd commented Jan 6, 2020

@zsirfs Could you please expand your question with more details? What does the request look like? What is your expectation?

@alejandrolorefice
Copy link

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

@ahhmarr
Copy link

ahhmarr commented Feb 9, 2021

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

@vkd
Copy link
Contributor

vkd commented Feb 9, 2021

@ahhmarr this should work for you:

type Student struct {
	ID                 uint32      `form:"id"`
	Dob                *Date      `form:"dob" json:"dob"`
	Name               string     `form:"name"` 
}

@ahhmarr
Copy link

ahhmarr commented Feb 9, 2021

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
	}

@vkd
Copy link
Contributor

vkd commented Feb 10, 2021

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

@dgqypl
Copy link

dgqypl commented Aug 17, 2021

refer to this: https://segmentfault.com/a/1190000022264001

@warjiang
Copy link

warjiang commented Jun 2, 2022

refer to this: https://segmentfault.com/a/1190000022264001

nice, a elegant solution~~

@chjiyun
Copy link

chjiyun commented Mar 26, 2023

@vkd iso time can pass the json decode, I have made a test to it

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