英文:
How to print struct with String() of fields?
问题
这段代码:
type A struct {
t time.Time
}
func main() {
a := A{time.Now()}
fmt.Println(a)
fmt.Println(a.t)
}
输出结果为:
{{63393490800 0 0x206da0}}
2009-11-10 23:00:00 +0000 UTC
A
没有实现 String()
方法,所以它不是一个 fmt.Stringer
,并打印其原生表示。但是,为每个要打印的结构体实现 String()
方法非常繁琐。更糟糕的是,如果我添加或删除一些字段,我还必须更新 String()
方法。有没有一种更简单的方法来打印一个结构体及其字段的 String()
值呢?
英文:
This code:
type A struct {
t time.Time
}
func main() {
a := A{time.Now()}
fmt.Println(a)
fmt.Println(a.t)
}
prints:
{{63393490800 0 0x206da0}}
2009-11-10 23:00:00 +0000 UTC
A
doesn't implement String()
, so it's not a fmt.Stringer
and prints its native representation. But is very tedious to implement String()
for every single struct I want to print. Worse, I have to update the String()
s if I add or remove some fields. Is there an easier way to print a struct, with its fields' String()
s?
答案1
得分: 4
这是fmt
包的实现方式,所以你不能改变它。
但是你可以编写一个辅助函数,使用反射(reflect
包)来迭代结构体的字段,并且如果字段有String()
方法,就调用该方法。
示例实现:
func PrintStruct(s interface{}, names bool) string {
v := reflect.ValueOf(s)
t := v.Type()
// 避免在s不是结构体时发生panic:
if t.Kind() != reflect.Struct {
return fmt.Sprint(s)
}
b := &bytes.Buffer{}
b.WriteString("{")
for i := 0; i < v.NumField(); i++ {
if i > 0 {
b.WriteString(" ")
}
v2 := v.Field(i)
if names {
b.WriteString(t.Field(i).Name)
b.WriteString(":")
}
if v2.CanInterface() {
if st, ok := v2.Interface().(fmt.Stringer); ok {
b.WriteString(st.String())
continue
}
}
fmt.Fprint(b, v2)
}
b.WriteString("}")
return b.String()
}
现在当你想要打印一个结构体时,可以这样做:
fmt.Println(PrintStruct(a, true))
你也可以选择在你的结构体中添加一个String()
方法,该方法只需调用我们的PrintStruct()
函数:
func (a A) String() string {
return PrintStruct(a, true)
}
每当你更改结构体时,你不需要对String()
方法做任何更改,因为它使用反射动态遍历所有字段。
注意:
由于我们使用了反射,你必须导出t time.Time
字段才能使其工作(还添加了一些额外的字段用于测试):
type A struct {
T time.Time
I int
unexported string
}
进行测试:
a := A{time.Now(), 2, "hi!"}
fmt.Println(a)
fmt.Println(PrintStruct(a, true))
fmt.Println(PrintStruct(a, false))
fmt.Println(PrintStruct("I'm not a struct", true))
输出结果(在Go Playground上尝试):
{T:2009-11-10 23:00:00 +0000 UTC I:2 unexported:hi!}
{T:2009-11-10 23:00:00 +0000 UTC I:2 unexported:hi!}
{2009-11-10 23:00:00 +0000 UTC 2 hi!}
I'm not a struct
英文:
This is how the fmt
package is implemented, so you can't change that.
But you can write a helper function which uses reflection (reflect
package) to iterate over the fields of a struct, and can call the String()
method on the fields if they have such a method.
Example implementation:
func PrintStruct(s interface{}, names bool) string {
v := reflect.ValueOf(s)
t := v.Type()
// To avoid panic if s is not a struct:
if t.Kind() != reflect.Struct {
return fmt.Sprint(s)
}
b := &bytes.Buffer{}
b.WriteString("{")
for i := 0; i < v.NumField(); i++ {
if i > 0 {
b.WriteString(" ")
}
v2 := v.Field(i)
if names {
b.WriteString(t.Field(i).Name)
b.WriteString(":")
}
if v2.CanInterface() {
if st, ok := v2.Interface().(fmt.Stringer); ok {
b.WriteString(st.String())
continue
}
}
fmt.Fprint(b, v2)
}
b.WriteString("}")
return b.String()
}
Now when you want to print a struct
, you can do:
fmt.Println(PrintStruct(a, true))
You may also choose to add a String()
method to your struct which just has to call our PrintStruct()
function:
func (a A) String() string {
return PrintStruct(a, true)
}
Whenever you change your struct, you don't have to do anything with your String()
method as it uses reflection to dynamically walk over all the fields.
Notes:
Since we're using reflection, you have to export the t time.Time
field for this to work (also added a few extra fields for testing purposes):
type A struct {
T time.Time
I int
unexported string
}
Testing it:
a := A{time.Now(), 2, "hi!"}
fmt.Println(a)
fmt.Println(PrintStruct(a, true))
fmt.Println(PrintStruct(a, false))
fmt.Println(PrintStruct("I'm not a struct", true))
Output (try it on the Go Playground):
{T:2009-11-10 23:00:00 +0000 UTC I:2 unexported:hi!}
{T:2009-11-10 23:00:00 +0000 UTC I:2 unexported:hi!}
{2009-11-10 23:00:00 +0000 UTC 2 hi!}
I'm not a struct
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论