如何使用字段的String()方法打印结构体?

huangapple go评论81阅读模式
英文:

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 := &amp;bytes.Buffer{}
	b.WriteString(&quot;{&quot;)
	for i := 0; i &lt; v.NumField(); i++ {
		if i &gt; 0 {
			b.WriteString(&quot; &quot;)
		}
		v2 := v.Field(i)
		if names {
			b.WriteString(t.Field(i).Name)
			b.WriteString(&quot;:&quot;)
		}
		if v2.CanInterface() {
			if st, ok := v2.Interface().(fmt.Stringer); ok {
				b.WriteString(st.String())
				continue
			}
		}
		fmt.Fprint(b, v2)
	}
	b.WriteString(&quot;}&quot;)
	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, &quot;hi!&quot;}
fmt.Println(a)
fmt.Println(PrintStruct(a, true))
fmt.Println(PrintStruct(a, false))
fmt.Println(PrintStruct(&quot;I&#39;m not a struct&quot;, 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&#39;m not a struct

huangapple
  • 本文由 发表于 2015年10月15日 15:37:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/33142594.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定