time.Time 类型别名的自定义解组函数

分享于2022年07月16日 go time unmarshalling 问答
【问题标题】:Custom unmarshal function for time.Time type aliastime.Time 类型别名的自定义解组函数
【发布时间】:2022-06-28 00:21:46
【问题描述】:

我正在尝试为自定义类型编写解组函数。 考虑下面的代码 ( playground )

package main

import (
    "encoding/json"
    "fmt"
    "strings"
    "time"
)

type Time time.Time

func (st *Time) UnmarshalJSON(b []byte) error {
    // "2021-05-21T03:10:20.958450" -> "2021-05-21T03:10:20.958450Z"
    s := strings.Trim(string(b), "\"")
    t, err := time.Parse(time.RFC3339, fmt.Sprintf("%s%s", s, "Z"))
    if err != nil {
        return fmt.Errorf("parse time: %w", err)
    }
    *st = Time(t)
    return nil
}

type User struct {
    Name string
    TS Time
}

const data = `{"id":3, "name":"Name", "ts":"2021-05-21T03:10:20.958450"}`

func main() {
    user := new(User)
    json.Unmarshal([]byte(data), &user)
    fmt.Printf("%v\n", user)
}

我成功地从我的 time.Parse() 获得了一个有效的 time.Time 值,但我不太明白为什么 *st = Time(t) 给出了如此奇怪的值?

目前打印出以上内容:

&{Name {958450000 63757163420 }}

但我想更类似于:

&{Name 2021-05-21 03:10:20.95845 +0000 UTC}

我在这里误解了什么?


【解决方案1】:

与 time.Time 相反,你的类型没有实现 fmt.Stringer ,所以 fmt.Print* 函数别无选择,只能使用它们的默认格式化逻辑,在这种情况下是打印底层 time.Time 的字段用大括号括起来的值。

添加一个 String 方法,将 time.Time.String 委托给您的类型以获得所需的行为:

func (t Time) String() string {
    return time.Time(t).String()
}

https://go.dev/play/p/5PwOwa49B5X

或者,将您的时间类型更改为嵌入 time.Time。这将自动提升 String 方法以及所有其他方法(例如 Marshal* 方法):

type Time struct {
    time.Time
}

func (st *Time) UnmarshalJSON(b []byte) error {
    // ...
    st.Time = t // simple assignment without type conversion
    return nil
}

https://go.dev/play/p/0H5qyCO22gu

此外,您永远不应该手动解析 JSON。 strings.Trim(string(b), "\"") 不足以完全解码 JSON 字符串值。始终使用 json.Unmarshal。您可以使用 time.ParseInLocation 进行简化。

func (st *Time) UnmarshalJSON(b []byte) error {
    var s string
    if err := json.Unmarshal(b, &s); err != nil {
        return err
    }

    t, err := time.ParseInLocation("2006-01-02T15:04:05", s, time.UTC)
    if err != nil {
        return err
    }

    // ...
}

【讨论】: