You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 10:10:34