Go语言中如何处理JSON反序列化time.Time的格式兼容问题?
我通过JOIN、CONCAT、GROUP_CONCAT、JSON_OBJECT语句从多张数据库表读取数据,并用GORM映射到如下模型:
type OrgUserDisPublisherData struct { Disciplines datatypes.JSON `json:"disciplines" example:"[]"` User datatypes.JSON `json:"user"` }
这一步执行正常,但将OrgUserDisPublisherData.Disciplines反序列化为包含time.Time类型的Discipline结构体时,出现错误:
parsing time "\"2022-11-03 07:08:09.000000\"" as "\"2006-01-02T15:04:05Z07:00\"": cannot parse " 07:08:09.000000\"" as "T"
反序列化使用的模型如下:
type Discipline struct { Name string `json:"name"` Code string `json:"code"` IsPrimary uint `json:"isPrimary"` IsAligned uint `json:"isAligned"` IsTrainingFaculty uint `json:"isTrainingFaculty"` AlignmentDate time.Time `json:"alignmentDate"` UnalignmentDate time.Time `json:"UnalignmentDate"` ExpiryDate time.Time `json:"expiryDate"` ExternalId string `json:"externalId"` Status string `json:"status"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` }
值得注意的是,插入数据时使用同一个Discipline模型并未出现任何时间相关错误,现在需要解决反序列化时时间字段兼容任意数据的问题。
方法一:自定义兼容多格式的时间类型
标准库time.Time仅支持默认的RFC3339格式,而数据库返回的是带引号且用空格分隔日期时间的格式,直接反序列化会失败。可以自定义一个时间类型,实现json.Unmarshaler接口,兼容多种时间格式:
import ( "strings" "time" ) type FlexibleTime time.Time // 实现JSON反序列化接口,处理多种时间格式 func (ft *FlexibleTime) UnmarshalJSON(data []byte) error { // 去除首尾的引号 str := strings.Trim(string(data), "\"") // 处理空值情况 if str == "null" || str == "" { *ft = FlexibleTime(time.Time{}) return nil } // 定义需要兼容的时间格式列表,可根据实际情况补充 formats := []string{ "2006-01-02 15:04:05.000000", // 数据库返回的格式 time.RFC3339, // JSON标准格式 "2006-01-02", // 仅日期格式 "2006-01-02 15:04:05", // 无毫秒的时间格式 } var t time.Time var err error // 遍历格式列表,尝试解析 for _, format := range formats { t, err = time.Parse(format, str) if err == nil { break } } if err != nil { return err } *ft = FlexibleTime(t) return nil } // 可选:实现JSON序列化接口,确保输出标准格式 func (ft FlexibleTime) MarshalJSON() ([]byte, error) { t := time.Time(ft) if t.IsZero() { return []byte("null"), nil } return []byte("\"" + t.Format(time.RFC3339) + "\""), nil } // 可选:提供转换为time.Time的方法,方便业务使用 func (ft FlexibleTime) ToTime() time.Time { return time.Time(ft) }
之后把Discipline结构体中的time.Time类型替换为自定义的FlexibleTime即可:
type Discipline struct { Name string `json:"name"` Code string `json:"code"` IsPrimary uint `json:"isPrimary"` IsAligned uint `json:"isAligned"` IsTrainingFaculty uint `json:"isTrainingFaculty"` AlignmentDate FlexibleTime `json:"alignmentDate"` UnalignmentDate FlexibleTime `json:"UnalignmentDate"` ExpiryDate FlexibleTime `json:"expiryDate"` ExternalId string `json:"externalId"` Status string `json:"status"` CreatedAt FlexibleTime `json:"createdAt"` UpdatedAt FlexibleTime `json:"updatedAt"` }
方法二:在SQL查询阶段统一时间格式
既然问题源于数据库返回的时间格式和JSON标准不兼容,直接在SQL中把时间字段转换为RFC3339格式即可。以MySQL为例,使用DATE_FORMAT函数:
JSON_OBJECT( 'alignmentDate', DATE_FORMAT(d.alignment_date, '%Y-%m-%dT%H:%i:%s.%fZ'), 'unalignmentDate', DATE_FORMAT(d.unalignment_date, '%Y-%m-%dT%H:%i:%s.%fZ'), 'expiryDate', DATE_FORMAT(d.expiry_date, '%Y-%m-%dT%H:%i:%s.%fZ'), 'createdAt', DATE_FORMAT(d.created_at, '%Y-%m-%dT%H:%i:%s.%fZ'), 'updatedAt', DATE_FORMAT(d.updated_at, '%Y-%m-%dT%H:%i:%s.%fZ'), -- 其他字段... )
如果是PostgreSQL,可使用TO_CHAR函数:
JSON_BUILD_OBJECT( 'alignmentDate', TO_CHAR(d.alignment_date, 'YYYY-MM-DD"T"HH24:MI:SS.US"Z"'), -- 其他字段... )
这样数据库返回的时间格式完全符合time.Time默认的解析要求,反序列化时就不会报错。
方法三:反序列化前预处理JSON数据
如果不想修改模型或SQL,可以在反序列化前对JSON字符串做预处理,把数据库格式的时间字符串替换为标准RFC3339格式:
import ( "encoding/json" "regexp" ) // 假设orgUserDisData是已经从数据库查询到的OrgUserDisPublisherData实例 disJSONStr := string(orgUserDisData.Disciplines) // 用正则表达式匹配数据库格式的时间字符串,替换为标准格式 re := regexp.MustCompile(`"(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}\.\d{6})"`) processedStr := re.ReplaceAllString(disJSONStr, `"$1T$2Z"`) // 处理后的字符串反序列化为Discipline切片 var disciplines []Discipline err := json.Unmarshal([]byte(processedStr), &disciplines) if err != nil { // 处理反序列化错误 }
补充说明
插入数据时没有问题,是因为GORM会自动把time.Time类型序列化为数据库能识别的格式;而查询时用JSON_OBJECT拼接的是数据库原生的时间字符串格式,和JSON标准的时间格式(RFC3339)不匹配,所以导致反序列化失败。
内容的提问来源于stack exchange,提问作者Midhun Kumar

