英文:
Unexpected output when printing time.Time type alias
问题
我正在尝试为自定义类型编写一个解组函数。考虑下面的代码(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 <nil>}}
但我希望输出更类似于:
&{Name 2021-05-21 03:10:20.95845 +0000 UTC}
我在这里理解错了什么?
英文:
I am trying to write a unmarshal function for a custom type.
Consider the below code (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)
}
I am successfully able to get a valid time.Time
value from my time.Parse()
but I am not quite grasping why *st = Time(t)
gives such a wierd value?
Currently the above prints out:
&{Name {958450000 63757163420 <nil>}}
But I would like to be more similar to:
&{Name 2021-05-21 03:10:20.95845 +0000 UTC}
What am I missunderstanding here?
答案1
得分: 5
与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.Time。这将自动提升String方法以及所有其他方法(例如Marshal*方法):
type Time struct {
time.Time
}
func (st *Time) UnmarshalJSON(b []byte) error {
// ...
st.Time = t // 简单赋值,无需类型转换
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
}
// ...
}
英文:
Contrary to time.Time your type does not implement fmt.Stringer, so the fmt.Print* functions have no choice but to use their default formatting logic, which in this case is to print the fields of the underlying time.Time value enclosed in curly braces.
Add a String method which delegates to time.Time.String to your type to get the desired behavior:
func (t Time) String() string {
return time.Time(t).String()
}
https://go.dev/play/p/5PwOwa49B5X
Alternatively, change your Time type to embed time.Time. This will automatically promote the String method along with all other methods (such as the Marshal* methods):
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
Also, you should never parse JSON by hand. strings.Trim(string(b), "\"")
is not enough to fully decode a JSON string value. Always use json.Unmarshal. And you can simplify by using 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
}
// ...
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论