内容简介:最近在解析遇到这个问题后,我在网上找了很多方案,发现都失败了。有的可以完成正常解析,但是无法正确写入到数据库。有的可以正常写入和写出,但是会使得
最近在解析 Go 的日期数据格式时( mysql 的 datetime 类型)时遇到个问题,在网上搜了很多方案都试了以后发现不可行,于是自己尝试解决后将解决方案发布出来。
Go 自身的 time.Time 类型默认解析的日期格式是 RFC3339 标准,也就是 2006-01-02T15:04:05Z07:00 的格式。如果我们想要在 Gin 的 shouldBindJSON 方法中,传入 YYYY-MM-DD hh:mm:ss 格式的日期格式作为 time.Time 类型的值,就会引发类似于 parsing time xx as xx: cannot parse xx as xx 的报错信息。这是因为 time.Time 类型默认支持的日期格式与我们传入的格式不同,导致解析出错。。
遇到这个问题后,我在网上找了很多方案,发现都失败了。有的可以完成正常解析,但是无法正确写入到数据库。有的可以正常写入和写出,但是会使得 gin 自带的验证规则如 binding:"required" 规则失效,失去校验的功能。
自定义 LocalTime 类型
解决这个问题的关键就是解决 c.ShouldBindJSON 和 gorm.Updates 的问题,我们需要定义一个新的 Time 类型和自定义的日期格式解析(如下),并将我们的 struct 结构体 datetime 字段指定为我们自定义的类型(如下)
- 自定义
LocalTime类型
// model.LocalTime package model const TimeFormat = "2006-01-02 15:04:05" type LocalTime time.Time
- 业务代码结构
// You Application Struct
package order
type OrderTest struct {
OrderId int `json:"order_id"`
Test string `json:"test"`
PaymentTime *model.LocalTime `json:"payment_time" binding:"required"`
TestTime *model.LocalTime `json:"test_time"`
}
解析 JSON 格式数据 - UnmarshalJSON 与 MarshalJSON
在 c.ShouldBindJSON 时,会调用 field.UnmarshalJSON 方法,所以我们需要先设置这个方法(如下):
func (t *LocalTime) UnmarshalJSON(data []byte) (err error) {
// 空值不进行解析
if len(data) == 2 {
*t = LocalTime(time.Time{})
return
}
// 指定解析的格式
now, err := time.Parse(`"`+TimeFormat+`"`, string(data))
*t = LocalTime(now)
return
}
在 UnmarshalJSON 解析后, shouldBindJSON 就可以正常解析 YYYY-MM-DD hh:mm:ss 格式的日期格式了,这样一来就解决了 parsing time xx as xx: cannot parse xx as xx 的问题。
既然解决了 shouldBindJSON 的问题,我们还需要解决 c.JSON 时解析值的问题(实现如下)
func (t LocalTime) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(TimeFormat)+2)
b = append(b, '"')
b = time.Time(t).AppendFormat(b, TimeFormat)
b = append(b, '"')
return b, nil
}
数据库写入和写出问题 - Value 与 Scan
在实现了 JSON 格式数据的解析取值后,会发现我们的值依然无法通过 gorm 被存储到 mysql 数据库中,通过抓包我们可以看看正常的请求和错误的请求的区别(见下图)
从 上图 1 (正常情况) 可以看出, payment_time 字段被传递,这样就可以正常存入更新。
从 上图 2(我们现在的情况) 可以看出,我们的 payment_time 字段根本没有被传递,从而导致更新失败。
所以这个问题属于 gorm 对字段取值的问题, gorm 内部是通过 Value 和 Scan 这两个方法完成值的写入和检出。那么从这个角度出发,我们就需要给我们的类型实现 Value 和 Scan 方法,分别对应写入的时候获取值和检出的时候解析值。(实现如下)
// 写入 mysql 时调用 func (t LocalTime) Value() (driver.Value, error) { // 0001-01-01 00:00:00 属于空值,遇到空值解析成 null 即可 if t.String() == "0001-01-01 00:00:00" { return nil, nil } return []byte(time.Time(t).Format(TimeFormat)), nil } // 检出 mysql 时调用 func (t *LocalTime) Scan(v interface{}) error { // mysql 内部日期的格式可能是 2006-01-02 15:04:05 +0800 CST 格式,所以检出的时候还需要进行一次格式化 tTime, _ := time.Parse("2006-01-02 15:04:05 +0800 CST", v.(time.Time).String()) *t = LocalTime(tTime) return nil } // 用于 fmt.Println 和后续验证场景 func (t LocalTime) String() string { return time.Time(t).Format(TimeFormat) }
如此一来,我们就可以正常解析存取 YYYY-MM-DD hh:mm:ss 格式的时间数据了(见下图)
LocalTime 完整代码如下:
package model
import (
"database/sql/driver"
"time"
)
const TimeFormat = "2006-01-02 15:04:05"
type LocalTime time.Time
func (t *LocalTime) UnmarshalJSON(data []byte) (err error) {
if len(data) == 2 {
*t = LocalTime(time.Time{})
return
}
now, err := time.Parse(`"`+TimeFormat+`"`, string(data))
*t = LocalTime(now)
return
}
func (t LocalTime) MarshalJSON() ([]byte, error) {
b := make([]byte, 0, len(TimeFormat)+2)
b = append(b, '"')
b = time.Time(t).AppendFormat(b, TimeFormat)
b = append(b, '"')
return b, nil
}
func (t LocalTime) Value() (driver.Value, error) {
if t.String() == "0001-01-01 00:00:00" {
return nil, nil
}
return []byte(time.Time(t).Format(TimeFormat)), nil
}
func (t *LocalTime) Scan(v interface{}) error {
tTime, _ := time.Parse("2006-01-02 15:04:05 +0800 CST", v.(time.Time).String())
*t = LocalTime(tTime)
return nil
}
func (t LocalTime) String() string {
return time.Time(t).Format(TimeFormat)
}
解决验证器 binding:"required" 无法正常工作
在完成上述步骤后,你的 go 应用已经可以正常存取自定义的日期格式格式了。但是还有一个问题,那就是 binding:"required" 并不能正常工作了,如果你传入一个空字符串 "" 日期数据,也会通过校验,并在数据库写入 null !
这个问题是因为 gin 内置的 validator 对我们的 model.LocalTime 还没有一个完善的空值检测机制,我们只需要加上这个检测机制即可。(实现如下)
package app
func ValidateJSONDateType(field reflect.Value) interface{} {
if field.Type() == reflect.TypeOf(model.LocalTime{}) {
timeStr := field.Interface().(model.LocalTime).String()
// 0001-01-01 00:00:00 是 go 中 time.Time 类型的空值
// 这里返回 Nil 则会被 validator 判定为空值,而无法通过 `binding:"required"` 规则
if timeStr == "0001-01-01 00:00:00" {
return nil
}
return timeStr
}
return nil
}
func Run() {
router := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 注册 model.LocalTime 类型的自定义校验规则
v.RegisterCustomTypeFunc(ValidateJSONDateType, model.LocalTime{})
}
}
加上这条自定义规则后,我们的校验规则又可以生效了,问题完美解决!(见下图)
这个问题困惑了我好几天,一开始想快点解决,在网上找了很多方案拿过来 copy 后,都没有解决问题。最后决定静下来心来,思考其背后的原理,仔细分析,最终靠自己攻克了这个问题,真是不容易。
这件事也让我明白了一个道理,授人予鱼不如授人予渔,所以我在这里也把解决问题的思路分享出来,希望对大家也能有一点理解上的提升。
最后一件事
如果本文对您有帮助的话,请点个赞和收藏吧!
您的点赞是对作者的最大鼓励,也可以让更多人看到本篇文章!
以上所述就是小编给大家介绍的《Go 自定义日期时间格式解析解决方案 - 解决 `parsing time xx as xx` 错误》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- XML解析错误:未组织好 的解决办法
- beego 解析json问题 小白在线等解决
- C语言解决八皇后问题代码及解析
- 以太坊 DAO 攻击解决方案代码解析
- 解析Silverlight调用WCF/Rest异常的解决方法
- 解析Silverlight调用WCF/Rest异常的解决方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
智能Web算法(第2版)
【英】Douglas G. McIlwraith(道格拉斯 G. 麦基尔雷思)、【美】Haralambos Marmanis(哈若拉玛 玛若曼尼斯)、【美】Dmitry Babenko(德米特里•巴邦科) / 达观数据、陈运文 等 / 电子工业出版社 / 2017-7 / 69.00
机器学习一直是人工智能研究领域的重要方向,而在大数据时代,来自Web 的数据采集、挖掘、应用技术又越来越受到瞩目,并创造着巨大的价值。本书是有关Web数据挖掘和机器学习技术的一本知名的著作,第2 版进一步加入了本领域最新的研究内容和应用案例,介绍了统计学、结构建模、推荐系统、数据分类、点击预测、深度学习、效果评估、数据采集等众多方面的内容。《智能Web算法(第2版)》内容翔实、案例生动,有很高的阅......一起来看看 《智能Web算法(第2版)》 这本书的介绍吧!